﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Data.Objects.SqlClient;
using System.Linq;
using System.Text;
using System.Xml.Linq;
using HQPad.Services;
using HQPad.DAL;
using HQPad.Models;

namespace HQPad.Helpers
{
    public enum AttendanceType
    {
        Player,
        Monster,
        Referee,
        Unknown
    }

    public enum SchoolStatusAssignmentType
    {
        Level1to8,
        Level9,
        Level10
    }

    public enum SectStatusAssignmentType
    {
        Level1to8,
        Level9,
        Level10
    }

    public enum ArmourType
    {
        NonMetal,
        Metal,
        Tabard,
        Headwear
    }

    public enum ItemType
    {
        Item,
        Scroll,
        Ability,
        Potion
    }

    public enum ItemUseType
    {
        Recharges,  // item has x charges per y time period.
        Unlimited,  // no limit to number of uses.
        AlwaysOn,   // always on item.
        OnceEver,   // One charge ever.
        Charged     // A limited number of charges.
    }

    /// <summary>
    /// Provides helper methods to process the character xml data
    /// and enforces the business rules associated with characters.
    /// </summary>
    public class CharacterRulesHelper
    {
        private const int SpellsPerStatusFactor = 3;
        private const int InvocationsPerStatusFactor = 3;
        private const int InvocationsPerStatusFactorSecondary = 2;
        private const int CosmicInvocationsPerStatusFactor = 2;
        private const string DateFormatStringForXml = "yyyy-MM-ddTHH:mm:ss.fffffffzzz"; 

        private XDocument characterData = null;
        private IUnitOfWork context;
        private int? ruleSetId;
        private ISkillService skillService;
        private ISpellService spellService;
        private IInvocationService invocationService;
        private IGuildService guildService;
        private IRulesService rulesService;
        private PointsTable pointsTable;

        // private members that are instance caches.
        private List<XElement> boughtSkills;
        private List<XElement> boughtSpells;
        private List<XElement> boughtInvocations;
        private List<QualityItem> bonuses;

        #region Bonuses
        /// <summary>
        /// All bonuses applicable to the character.
        /// </summary>
        protected List<QualityItem> Bonuses
        {
            get
            {
                if (bonuses == null)
                {
                    bonuses = GetBonuses();
                }

                return bonuses;
            }
        }
        #endregion

        #region Table
        protected PointsTable Table
        {
            get
            {
                if (pointsTable == null)
                {
                    pointsTable = RulesService.GetPointsTable(Class(), Race());

                    if (pointsTable == null)
                        throw new ApplicationException(string.Format("Unable to find points table for {1} {0}.", Class(), Race()));
                }

                return pointsTable;
            }
        }
        #endregion

        #region GuildService
        protected IGuildService GuildService
        {
            get
            {
                if (guildService == null)
                    guildService = new GuildService(context);

                return guildService;
            }
        }
        #endregion

        #region SkillService
        protected ISkillService SkillService
        {
            get
            {
                if (skillService == null)
                    skillService = new SkillService(context);

                return skillService;
            }
        }
        #endregion

        #region RulesService
        protected IRulesService RulesService
        {
            get
            {
                if (rulesService == null)
                    rulesService = new RulesService(context);

                return rulesService;
            }
        }
        #endregion

        #region SpellService
        protected ISpellService SpellService
        {
            get
            {
                if (spellService == null)
                    spellService = new SpellService(context);

                return spellService;
            }
        }
        #endregion

        #region InvocationService
        protected IInvocationService InvocationService
        {
            get
            {
                if (invocationService == null)
                    invocationService = new InvocationService(context);

                return invocationService;
            }
        }
        #endregion

        #region RuleSetID
        /// <summary>
        /// Property to expose the current rule set id.
        /// </summary>
        protected int RuleSetID
        {
            get
            {
                if (ruleSetId.HasValue)
                    return ruleSetId.Value;

                ruleSetId = RulesService.GetDefaultRules().RuleSetID;

                return ruleSetId.Value;
            }
        }
        #endregion

        public CharacterRulesHelper(IUnitOfWork context, string xml)
        {
            this.context = context;
            characterData = XDocument.Parse(xml);
        }

        #region CreateDefaultCharacter
        /// <summary>
        /// Create a default character.
        /// </summary>
        /// <returns>A character xml document with default settings.</returns>
        public static XDocument CreateDefaultCharacter()
        {
            // get rules version.
            IUnitOfWork context = new HQContext();
            IRulesService rulesService = new RulesService(context);

            RuleSet rules = rulesService.GetDefaultRules();

            string rulesVersion = string.Format("{0}.{1}", rules.MajorVersion, rules.MinorVersion);

            XDocument data = 
                new XDocument(
                    new XElement(
                        "hqpad", 
                        new XAttribute("version", rulesVersion),
                        new XElement(
                            "character", 
                            new XAttribute("name","mystery hero"),
                            new XAttribute("points", "0"),
                            new XAttribute("effectivepoints", "0"),
                            new XAttribute("status", "0"),
                            new XAttribute("spentpoints", "0"),
                            new XAttribute("spentstatus", "0"),
                            new XAttribute("rank", "0"),
                            new XAttribute("race", "Human"),
                            new XAttribute("class", "Warrior")
                        )
                    ));

            return data;
        }
        #endregion

        #region GetXml
        /// <summary>
        /// Get the character xml document as a string.
        /// </summary>
        /// <returns></returns>
        public string GetXml()
        {
            return characterData.ToString();
        }
        #endregion

        /// <summary>
        /// Update the current xml state to a scratch pad character.
        /// </summary>
        /// <param name="character">The character to update.</param>
        public void SaveToCharacter(ScratchPad character)
        {
            character.Xml = GetXml();
        }

        #region GetCharacterElement
        /// <summary>
        /// Get the character element from the character data.
        /// </summary>
        /// <returns></returns>
        public XElement GetCharacterElement()
        {
            if (characterData == null)
                throw new ApplicationException("Character data is null");

            XElement characterElement = characterData.Descendants("character").FirstOrDefault();

            if (characterElement == null)
                throw new ApplicationException("Character xml is invalid: missing character element.");

            return characterElement;
        }
        #endregion

        #region PointsAvailable
        /// <summary>
        /// Get the total points available to the character.
        /// Although taken from an attribute this should match the sum of points in
        /// the adventure record.
        /// </summary>
        /// <returns></returns>
        public int PointsAvailable()
        {
            XElement characterElement = GetCharacterElement();

            XAttribute pointsAttribute = characterElement.Attribute("points");

            int points = 0;

            if (pointsAttribute != null && int.TryParse(pointsAttribute.Value, out points))
                return points;

            return 0;
        }
        #endregion

        #region EffectivePoints
        /// <summary>
        /// Get the effective points.
        /// Points are halved post rank 64 for all classes except scout.
        /// </summary>
        /// <returns>The effective character points for the character.</returns>
        public int EffectivePoints()
        {
            int points = PointsAvailable();
            string characterClass = Class();

            if (points <= 640 || characterClass == "Scout")
                return points;

            decimal remainder = (decimal)points - 640M;

            return 640 + Convert.ToInt32(Math.Round((remainder / 2),0, MidpointRounding.AwayFromZero));
        }
        #endregion

        #region PointsUnspent
        /// <summary>
        /// Get the overall unspent points available to the character.
        /// Although taken from an attribute this should match the sum of points in
        /// the adventure record less those spent on skills.
        /// </summary>
        /// <returns></returns>
        public int PointsUnspent()
        {
            int totalPoints = EffectivePoints();

            XElement characterElement = GetCharacterElement();

            XAttribute pointsAttribute = characterElement.Attribute("spentpoints");

            int points = 0;

            if (pointsAttribute != null && int.TryParse(pointsAttribute.Value, out points))
                return totalPoints - points;

            return 0;
        }
        #endregion

        #region StatusAvailable
        /// <summary>
        /// Get the overall status available to the character.
        /// Although taken from an attribute thier rank as calculated from the sum of points in
        /// the adventure record.
        /// </summary>
        /// <returns></returns>
        public int StatusAvailable()
        {
            XElement characterElement = GetCharacterElement();

            XAttribute statusAttribute = characterElement.Attribute("status");

            int status = 0;

            if (statusAttribute != null && int.TryParse(statusAttribute.Value, out status))
                return status;

            return 0;
        }
        #endregion

        #region StatusUnspent
        /// <summary>
        /// Get the overall unspentstatus available to the character.
        /// Although taken from an attribute thier rank as calculated from the sum of points in
        /// the adventure record less any spent on skills.
        /// </summary>
        /// <returns></returns>
        public int StatusUnspent()
        {
            int totalStatus = StatusAvailable();

            XElement characterElement = GetCharacterElement();

            XAttribute statusAttribute = characterElement.Attribute("spentstatus");

            int status = 0;

            if (statusAttribute != null && int.TryParse(statusAttribute.Value, out status))
                return totalStatus - status;

            return 0;
        }
        #endregion

        #region Rank
        /// <summary>
        /// Get the character rank.
        /// Although taken from an attribute this should match the sum of points in
        /// the adventure record divided by 10 rounded down.
        /// </summary>
        /// <returns></returns>
        public int Rank()
        {
            XElement characterElement = GetCharacterElement();

            XAttribute rankAttribute = characterElement.Attribute("rank");

            int rank = 0;

            if (rankAttribute != null && int.TryParse(rankAttribute.Value, out rank))
                return rank;

            return 0;
        }
        #endregion

        #region Race
        /// <summary>
        /// Get the character race.
        /// </summary>
        /// <returns></returns>
        public string Race()
        {
            XElement characterElement = GetCharacterElement();

            XAttribute raceAttribute = characterElement.Attribute("race");

            if (raceAttribute == null)
                return "Human";
            else
                return raceAttribute.Value;
        }
        #endregion

        #region Class
        /// <summary>
        /// Get the character class.
        /// </summary>
        /// <returns></returns>
        public string Class()
        {
            XElement characterElement = GetCharacterElement();

            XAttribute classAttribute = characterElement.Attribute("class");

            if (classAttribute == null)
                return "Warrior";
            else
                return classAttribute.Value;
        }
        #endregion

        #region SetRace
        /// <summary>
        /// Set a new race for the character.
        /// This changes the points table and therefore all the
        /// skill costs which must be recalculated.
        /// </summary>
        /// <param name="race">The race to assing.</param>
        public void SetRace(string race)
        {
            // todo: Validate race.

            // Set the race value in the character element.
            XElement characterElement = GetCharacterElement();

            XAttribute raceAttribute = characterElement.Attribute("race");

            if (raceAttribute == null)
            {
                raceAttribute = new XAttribute("race", race);
                characterElement.Add(raceAttribute);
            }
            else
                raceAttribute.SetValue(race);

            CalculatePointsSpent();
        }
        #endregion

        #region SetClass
        /// <summary>
        /// Set a new class for the character.
        /// This changes the points table and therefore all the
        /// skill costs which must be recalculated.
        /// </summary>
        /// <param name="characterClass">The race to assing.</param>
        public void SetClass(string characterClass)
        {
            // todo: Validate race.

            // Set the race value in the character element.
            XElement characterElement = GetCharacterElement();

            XAttribute classAttribute = characterElement.Attribute("class");

            if (classAttribute == null)
            {
                classAttribute = new XAttribute("class", characterClass);
                characterElement.Add(classAttribute);
            }
            else
                classAttribute.SetValue(characterClass);

            CalculatePointsSpent();
        }
        #endregion

        #region SetName
        /// <summary>
        /// Set the name of the character.
        /// </summary>
        /// <param name="name"></param>
        public void SetName(string name)
        {
            // Set the race value in the character element.
            XElement characterElement = GetCharacterElement();

            XAttribute nameAttribute = characterElement.Attribute("name");

            if (nameAttribute == null)
            {
                nameAttribute = new XAttribute("name", name);
                characterElement.Add(nameAttribute);
            }
            else
                nameAttribute.SetValue(name);
        }
        #endregion

        #region GetName
        /// <summary>
        /// Get the character name.
        /// </summary>
        /// <returns></returns>
        public string GetName()
        {
            // Get the name value in the character element.
            XElement characterElement = GetCharacterElement();

            XAttribute nameAttribute = characterElement.Attribute("name");

            if (nameAttribute == null)
                return string.Empty;
                
            return nameAttribute.Value;
        }
        #endregion


        /*
         * Skill lists and updating
         */

        #region GetSkills
        /// <summary>
        /// Get the list of skills available to the character.
        /// Any skills bought (but not necessarily available any more) 
        /// are included so that point allocations can be amended.
        /// </summary>
        /// <returns></returns>
        public List<SkillItem> GetSkills(bool showAllSkills)
        {
            List<SkillItem> skills = new List<SkillItem>();

            List<SkillView> allowedSkills = GetAllowedSkills(showAllSkills);

            // Get any bonus allowed skills and add them to the skill list if not already present.
            var bonusAllowedSkills = GetBonusesByType(BonusTypes.AllowSkill).Select(s => s.Value);

            foreach (string skillSlug in bonusAllowedSkills)
            {
                if (skills.Any(a => a.Slug == skillSlug))
                    continue;

                // Add a new new skill item record.
                Skill skill = SkillService.GetSkill(RuleSetID, skillSlug);
                allowedSkills.Add(new SkillView(skill, true));
            }
            
            List<XElement> boughtSkills = GetBoughtSkills().ToList();

            var boughtList = 
                from bs in boughtSkills
                where bs.Attribute("name") != null &&
                      !string.IsNullOrEmpty(bs.Attribute("name").Value)
                select new 
                {
                    Slug = bs.Attribute("name").Value,
                    Ranks = (bs.Attribute("ranks") != null) ? Convert.ToInt32(bs.Attribute("ranks").Value) : 0
                };

            foreach (var item in allowedSkills)
            {
                int? boughtRanks = boughtList.Where(b => b.Slug == item.Slug).Select(b => b.Ranks).FirstOrDefault();

                skills.Add(CreateSkillItemRecord(item, boughtRanks.HasValue ? boughtRanks.Value : 0));
            }

            // remove processed skills from the list.
            boughtSkills.RemoveAll(b =>  boughtList.Select(a => a.Slug).Contains(b.Attribute("name").Value));

            // Now tidy up any other skills bought but not available.
            foreach (XElement boughtSkill in boughtSkills)
            {
                if (boughtSkill.Attribute("name") == null || string.IsNullOrEmpty(boughtSkill.Attribute("name").Value))
                    continue;

                string slug = boughtSkill.Attribute("name").Value;

                SkillView skill = new SkillView(SkillService.GetSkill(RuleSetID, slug), true);

                if (skill == null)
                {
                    boughtSkill.Remove();  // Ditch skill as invalid.
                    continue;
                }

                // get ranks.
                int ranks = 0;

                if (boughtSkill != null && boughtSkill.Attribute("ranks") != null)
                    int.TryParse(boughtSkill.Attribute("ranks").Value, out ranks);

                skills.Add(CreateSkillItemRecord(skill, ranks));
            }
            return skills;
        }
        #endregion

        #region CreateSkillItemRecord
        /// <summary>
        /// Create  a skill item model record from a skill object.
        /// </summary>
        /// <param name="skill">The source skill.</param>
        /// <returns>The skill item filled with both copied and calculated properties.</returns>
        protected SkillItem CreateSkillItemRecord(SkillView skill, int ranks)
        {
            // todo: calculate cost.
            int status = 0;
            int cost = CalculateSkillCost(skill, ranks, out status);

            string costString = BuildCostString(skill);

            int? maxRanks = CalculateSkillMaxRanks(skill);

            SkillItem item = new SkillItem()
            {
                Name = skill.Name,
                Ranks = ranks,
                Cost = cost,
                CostDescription = costString,
                Status = status,
                MaxRanks = maxRanks,
                StatusSkill = skill.Status.HasValue && skill.Status.Value > 0,
                Slug = skill.Slug,
                Group = skill.TypeOfSkill,
                Description = skill.Description,
                Allowed = skill.Allowed
            };

            //// Determine if any choice values need to be added.  These are values for a drop such as schools of magic or sect lists.
            //skill.

            return item;
        }
        #endregion

        #region UpdateSkill
        /// <summary>
        /// Update the number of ranks assigned to a skill.
        /// If the skill has not been previously bought then
        /// a new record is created for it.
        /// </summary>
        /// <param name="skillId"></param>
        /// <param name="ranks"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        public bool UpdateSkill(string skillSlug, int ranks, out string reason, out List<SkillItemChange> changes, bool showAllSkills)
        {
            changes = new List<SkillItemChange>();
            bool success = false;
            reason = "Update ok";

            // Get the skill.
            Skill skill = SkillService.GetSkill(RuleSetID, skillSlug);

            if (skill == null)
            {
                reason = string.Format("Invalid skill id {0}.", skillSlug);
                return false;
            }

            int? maxRanks = CalculateSkillMaxRanks(skill);

            if (maxRanks.HasValue && maxRanks.Value < ranks)
            {
                reason = string.Format("The skill {0} is limited to {1} ranks.", skill.Name, skill.MaxRanks.Value);
                return success;
            }

            // Get the skills available before we update the skill.
            List<SkillItem> beforeSkills = GetSkills(showAllSkills);

            // Try and get any existing bought skill.
            List<XElement> skills = GetBoughtSkills()
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == skill.Slug)
                .ToList();

            if (skills != null && skills.Count > 0)
            {
                bool done = false;
                foreach (XElement skillElement in skills)
                {
                    if (done)
                    {
                        skillElement.Remove(); // remove any duplicates of this skill.
                        continue;
                    }

                    if (ranks == 0)
                    {
                        skillElement.Remove();
                        InvalidateBoughtSkillsCache();
                    }
                    else
                    {
                        // get ranks attribute and update.
                        XAttribute ranksAttribute = skillElement.Attribute("ranks");

                        if (ranksAttribute == null)
                        {
                            ranksAttribute = new XAttribute("ranks", ranks);
                            skillElement.Add(ranksAttribute);
                        }
                        else
                            ranksAttribute.SetValue(ranks);

                        done = true;
                    }
                }
            }
            else
            {
                // Create a new bought skill record.
                XElement skillsElement = characterData.Descendants("skills").FirstOrDefault();

                if (skillsElement == null)
                {
                    skillsElement = new XElement("skills");
                    characterData.Element("hqpad").Add(skillsElement);
                }

                skillsElement.Add(
                    new XElement("skill",
                        new XAttribute("name", skill.Slug),
                        new XAttribute("ranks", ranks)
                    )
                );

                InvalidateBoughtSkillsCache(); // refresh the bought skills cache as we've added another.
            }

            CalculatePointsSpent();

            changes = DetermineSkillChangeSet(beforeSkills, showAllSkills);

            return true;
        }

        /// <summary>
        /// Update the number of ranks assigned to a skill.
        /// If the skill has not been previously bought then
        /// a new record is created for it.
        /// </summary>
        /// <param name="skillId"></param>
        /// <param name="ranks"></param>
        /// <param name="reason"></param>
        /// <returns></returns>
        public bool UpdateSkill(string skillSlug, int ranks, out string reason)
        {
            bool success = false;
            reason = "Update ok";

            // Get the skill.
            Skill skill = SkillService.GetSkill(RuleSetID, skillSlug);

            if (skill == null)
            {
                reason = string.Format("Invalid skill id {0}.", skillSlug);
                return false;
            }

            int? maxRanks = CalculateSkillMaxRanks(skill);

            if (maxRanks.HasValue && maxRanks.Value < ranks)
            {
                reason = string.Format("The skill {0} is limited to {1} ranks.", skill.Name, skill.MaxRanks.Value);
                return success;
            }

            //// Get the skills available before we update the skill.
            //List<SkillItem> beforeSkills = GetSkills();

            // Try and get any existing bought skill.
            List<XElement> skills = GetBoughtSkills()
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == skill.Slug)
                .ToList();

            if (skills != null && skills.Count > 0)
            {
                bool done = false;
                foreach (XElement skillElement in skills)
                {
                    if (done)
                    {
                        skillElement.Remove(); // remove any duplicates of this skill.
                        continue;
                    }

                    if (ranks == 0)
                    {
                        skillElement.Remove();

                        // Invoke any special added skill handling.
                        if (skill.Slug == "make-focus")
                        {
                            // Add pre-bought spells if the character is a primary mage.
                            Guild guild = GuildService.GetGuild(RuleSetID, GuildService.CreateGuildSlug(PrimaryGuild()));

                            if (guild.PrimaryClass == "Mage" && !string.IsNullOrEmpty(guild.MagicColour))
                            {
                                // Get specialist spell for colour.
                                string specialistSpell =
                                    (from s in SpellService.GetSpells(RuleSetID)
                                     where s.Colour == guild.MagicColour && s.IsSpecialist == true
                                     select s.GroupSlug)
                                     .FirstOrDefault();

                                string[] spells = new string[4] { "dispel-magic-6", "dispel-magic-7", "dispel-magic-8", specialistSpell };

                                foreach (string spellSlug in spells)
                                {
                                    UpdateSpell(spellSlug, false, false, true);
                                }
                            }
                        }

                        InvalidateBoughtSkillsCache();
                    }
                    else
                    {
                        // get ranks attribute and update.
                        XAttribute ranksAttribute = skillElement.Attribute("ranks");

                        if (ranksAttribute == null)
                        {
                            ranksAttribute = new XAttribute("ranks", ranks);
                            skillElement.Add(ranksAttribute);
                        }
                        else
                            ranksAttribute.SetValue(ranks);

                        done = true;
                    }
                }
            }
            else
            {
                // Create a new bought skill record.
                XElement skillsElement = characterData.Descendants("skills").FirstOrDefault();

                if (skillsElement == null)
                {
                    skillsElement = new XElement("skills");
                    characterData.Element("hqpad").Add(skillsElement);
                }

                skillsElement.Add(
                    new XElement("skill",
                        new XAttribute("name", skill.Slug),
                        new XAttribute("ranks", ranks)
                    )
                );

                // Invoke any special added skill handling.
                if (skill.Slug == "make-focus")
                {
                    // Add pre-bought spells if the character is a primary mage.
                    Guild guild = GuildService.GetGuild(RuleSetID, GuildService.CreateGuildSlug(PrimaryGuild()));

                    if (guild.PrimaryClass == "Mage" && !string.IsNullOrEmpty(guild.MagicColour))
                    {
                        // Get specialist spell for colour.
                        string specialistSpell =
                            (from s in SpellService.GetSpells(RuleSetID)
                             where s.Colour == guild.MagicColour && s.IsSpecialist == true
                             select s.GroupSlug)
                             .FirstOrDefault();

                        string[] spells = new string[4] { "dispel-magic-6", "dispel-magic-7", "dispel-magic-8", specialistSpell };

                        foreach (string spellSlug in spells)
                        {
                            UpdateSpell(spellSlug, true, false, true);
                        }
                    }
                }

                InvalidateBoughtSkillsCache(); // refresh the bought skills cache as we've added another.
            }

            CalculatePointsSpent();

            return true;
        }
        #endregion

        #region DetermineSkillChangeSet
        /// <summary>
        /// Determine which skills have changes or been added/removed when comparing the current set
        /// to an arbitary set from the past.
        /// </summary>
        /// <param name="beforeSkills">The arbitary set of skills (typically before some other operation).</param>
        /// <returns>The list of skill changes.</returns>
        protected List<SkillItemChange> DetermineSkillChangeSet(List<SkillItem> beforeSkills, bool showAllSkills)
        {
            List<SkillItemChange> changes = new List<SkillItemChange>();
            IEnumerable<string> beforeSlugs = beforeSkills.Select(s => s.Slug);

            // Get the skills available after we update the skill and create a change set.
            List<SkillItem> afterSkills = GetSkills(showAllSkills);

            IEnumerable<string> afterSlugs = afterSkills.Select(s => s.Slug);

            // Get skills that have changed
            List<SkillItem> changedList =
                afterSkills.Where(b => beforeSlugs.Contains(b.Slug) && !beforeSkills.Where(a => a.Slug == b.Slug).First().Equals(b))
                .ToList();

            foreach (SkillItem item in changedList)
            {
                changes.Add(new SkillItemChange()
                {
                    Item = item,
                    Updated = true,
                    Added = false,
                    Removed = false
                });
            }

            // Get skills that have been added.

            List<SkillItem> addedList =
                afterSkills.Where(a => !beforeSlugs.Contains(a.Slug))
                .ToList();

            foreach (SkillItem item in addedList)
            {
                changes.Add(new SkillItemChange()
                {
                    Item = item,
                    Updated = false,
                    Added = true,
                    Removed = false
                });
            }

            // Get skills that have been removed. If they are in the bought list still include them.

            List<SkillItem> removedList =
                beforeSkills.Where(a => !afterSlugs.Contains(a.Slug))
                .ToList();

            foreach (SkillItem item in removedList)
            {
                changes.Add(new SkillItemChange()
                {
                    Item = item,
                    Updated = false,
                    Added = false,
                    Removed = true
                });
            }

            return changes;
        }
        #endregion

        #region CalculateSkillMaxRanks
        /// <summary>
        /// Calculate the maximum number of ranks for a skill.
        /// This is based on the skill max ranks property but bonuses
        /// can increase the number of ranks available.
        /// </summary>
        /// <param name="skill">The source skill.</param>
        /// <returns>The number of max ranks for the character.</returns>
        public int? CalculateSkillMaxRanks(SkillView skill)
        {
            if (!skill.MaxRanks.HasValue)
                return null; // no limit.

            int max = skill.MaxRanks.Value;

            return CalculateSkillMaxRanks(max, skill.Slug);
        }

        public int? CalculateSkillMaxRanks(Skill skill)
        {
            if (!skill.MaxRanks.HasValue)
                return null; // no limit.

            int max = skill.MaxRanks.Value;

            return CalculateSkillMaxRanks(max, skill.Slug);
        }

        public int? CalculateSkillMaxRanks(int maxRanks, string slug)
        {
            int max = maxRanks;

            IEnumerable<QualityItem> bonuses = GetBonusesByType(BonusTypes.MaxSkillRanks);

            bonuses = bonuses.Where(b => b.Value != null && b.Value.ToLowerInvariant().Contains(slug));

            foreach (QualityItem bonus in bonuses)
                max += bonus.Quantity;

            return max;
        }
        #endregion

        #region GetBoughtSkills
        /// <summary>
        /// Get all skills that the character has bought ranks of.
        /// </summary>
        /// <returns></returns>
        protected IEnumerable<XElement> GetBoughtSkills()
        {
            if (boughtSkills == null)
            {
                boughtSkills = characterData.Descendants("skill")
                    .Where(s =>
                        s.Attribute("ranks") != null &&
                        !string.IsNullOrEmpty(s.Attribute("ranks").Value) &&
                        Convert.ToInt32(s.Attribute("ranks").Value) > 0)
                    .ToList();
            }

            return boughtSkills;
        }
        #endregion

        #region GetBoughtSkill
        /// <summary>
        /// Get a bought skill by slug.
        /// </summary>
        /// <param name="skillSlug">The skill slug name.</param>
        /// <returns>The skill bought or null if not bought.</returns>
        protected XElement GetBoughtSkill(string skillSlug)
        {
            return GetBoughtSkills().Where(a => a.Attribute("name") != null && a.Attribute("name").Value == skillSlug).FirstOrDefault();
        }
        #endregion

        #region InvalidateBoughtSkillsCache
        /// <summary>
        /// Invalidate the cached set of bought skills.
        /// </summary>
        protected void InvalidateBoughtSkillsCache()
        {
            boughtSkills = null;
        }
        #endregion

        #region GetAllowedSkills
        /// <summary>
        /// Get all the allowed skills for the character.
        /// <list type="bullet">
        ///     <listheader>A skill is allowed if:</listheader>
        ///     <item>The skill is a basic skill.</item>
        ///     <item>All the requirements for the skill are met.</item>
        ///     <item>An item or ability grants the skill through the bonus system.</item>
        /// </list>
        /// </summary>
        /// <returns></returns>
        protected List<SkillView> GetAllowedSkills(bool showAllSkills)
        {
            //List<Skill> allowedSkills = new List<Skill>();

            // populate context with bought skills.
            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            StringBuilder skillList = new StringBuilder();
            StringBuilder ranksList = new StringBuilder();

            foreach (XElement boughtSkill in boughtSkills)
            {
                string skillName = boughtSkill.Attribute("name").Value;
                string ranks = (boughtSkill.Attribute("ranks") != null && !string.IsNullOrEmpty(boughtSkill.Attribute("ranks").Value)) ? boughtSkill.Attribute("ranks").Value : "1";

                if (skillList.Length > 0)
                {
                    skillList.Append(",");
                    ranksList.Append(",");
                }

                skillList.Append(skillName);
                ranksList.Append(ranks);
            }

            // add guild rank to context.
            int guildLevel = GuildLevel();
            
            // add guilds to context.
            string primaryGuild = PrimaryGuild();
            string secondaryGuild = SecondaryGuild();
            string tertiaryGuild = TertiaryGuild();

            if (!string.IsNullOrEmpty(primaryGuild))
                primaryGuild = GuildService.CreateGuildSlug(primaryGuild);

            if (!string.IsNullOrEmpty(secondaryGuild))
                secondaryGuild = GuildService.CreateGuildSlug(secondaryGuild);

            if (!string.IsNullOrEmpty(tertiaryGuild))
                tertiaryGuild = GuildService.CreateGuildSlug(tertiaryGuild);

            // add character rank to context.
            int rank = Rank();

            // Execute store procedure to get allowed skills.
            return SkillService.GetAllowedSkills(RuleSetID, skillList.ToString(), ranksList.ToString(), guildLevel, primaryGuild, secondaryGuild, tertiaryGuild, rank, Table.PointsTableID, showAllSkills).ToList();
        }
        #endregion

        #region HasTalisman
        /// <summary>
        /// Determine whether the character has a talisman.
        /// </summary>
        /// <returns></returns>
        public bool HasTalisman()
        {
            bool hasTalisman = false;

            XElement hasTalismanSkill = GetBoughtSkill("make-talisman");
            int ranks = 0;

            if (hasTalismanSkill != null &&
                hasTalismanSkill.Attribute("ranks") != null &&
                int.TryParse(hasTalismanSkill.Attribute("ranks").Value, out ranks))
            {
                hasTalisman = (ranks > 0);
            }

            return hasTalisman;
        }
        #endregion

        /*
         * Guilds
         */

        #region PrimaryGuild
        /// <summary>
        /// Get the primary guild name.
        /// </summary>
        /// <returns>The guild name or an empty string if non guild selected.</returns>
        public string PrimaryGuild()
        {
            return GetGuildByType("primary");
        }
        #endregion

        #region SecondaryGuild
        /// <summary>
        /// Get the secondary guild name.
        /// </summary>
        /// <returns>The guild name or an empty string if non guild selected.</returns>
        public string SecondaryGuild()
        {
            return GetGuildByType("secondary");
        }
        #endregion

        #region TertiaryGuild
        /// <summary>
        /// Get the tertiary guild name.
        /// </summary>
        /// <returns>The guild name or an empty string if non guild selected.</returns>
        public string TertiaryGuild()
        {
            return GetGuildByType("tertiary");
        }
        #endregion

        #region TertiaryGuildAllowed
        /// <summary>
        /// Whether a tertiary guild is allowed.
        /// </summary>
        /// <returns></returns>
        public bool TertiaryGuildAllowed()
        {
            string reason = string.Empty;

            return TertiaryGuildAllowed(out reason);
        }

        /// <summary>
        /// Whether a tertiary guild is allowed.
        /// </summary>
        /// <returns></returns>
        public bool TertiaryGuildAllowed(out string reason)
        {
            reason = string.Empty;

            bool allowedTertiary = false;

            // Anybody with the AllowTertiaryGuild bonus can have a tertiary guild.
            int bonusCount = GetBonusesByType(BonusTypes.AllowTertiaryGuild).Count();
            allowedTertiary = (bonusCount > 0);

            string primarySlug = GuildService.CreateGuildSlug(PrimaryGuild());
            string secondarySlug = GuildService.CreateGuildSlug(SecondaryGuild());

            Guild primaryGuild = GuildService.GetGuild(RuleSetID, primarySlug);
            Guild secondaryGuild = GuildService.GetGuild(RuleSetID, secondarySlug);

            if (primaryGuild == null)
            {
                reason = "You must have a primary guild to select a tertiary guild.";
                return false;
            }

            if (secondaryGuild == null)
            {
                reason = "You must have a secondary guild to select a tertiary guild.";
                return false;
            }

            if (allowedTertiary)
                return true;

            // Any Mage character can have three mage only guilds.
            if (Class() == "Mage")
            {
                // Check primary and secondary are mage only.
                if (secondaryGuild.PrimaryClass != "Mage")
                {
                    reason = "Mage characters can only have a tertiary guild if the secondary guild is a mage primary guild (school of magic).";
                    return false;
                }
                else
                    return true;
            }

            // Any acolyte can have three acolyte only guilds.
            if (Class() == "Acolyte")
            {
                // Check primary and secondary are mage only.
                if (secondaryGuild.PrimaryClass != "Acolyte")
                {
                    reason = "Acolyte characters can only have a tertiary guild if the secondary guild is an acolyte primary guild (sect).";
                    return false;
                }
                else
                    return true;
            }

            return false;
        }
        #endregion

        #region GetAllowedGuildList
        /// <summary>
        /// Get a list of all allowed secondary guilds.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Guild> GetAllowedGuildList()
        {
            // check there is a primary guild.
            string primaryGuild = PrimaryGuild();

            if (string.IsNullOrEmpty(primaryGuild))
                return null;

            string primarySlug = GuildService.CreateGuildSlug(primaryGuild);

            IEnumerable<Guild> allowedList = guildService.GetAllowedGuilds(RuleSetID, primarySlug);

            return allowedList;
        }
        #endregion

        #region GetAllowedTertiaryGuildList
        /// <summary>
        /// Get a list of all allowed tertiary guilds.
        /// </summary>
        /// <returns></returns>
        public IEnumerable<Guild> GetAllowedTertiaryGuildList()
        {
            // check there is a primary guild.
            string primaryGuildName = PrimaryGuild();

            if (string.IsNullOrEmpty(primaryGuildName))
                return null;

            string primarySlug = GuildService.CreateGuildSlug(primaryGuildName);

            IEnumerable<Guild> allowedList = guildService.GetAllowedGuilds(RuleSetID, primarySlug);

            // Anybody with the AllowTertiaryGuild bonus can have a tertiary guild.
            int bonusCount = GetBonusesByType(BonusTypes.AllowTertiaryGuild).Count();
            bool allowedAnyTertiary = (bonusCount > 0);

            if (allowedAnyTertiary)
                return allowedList;

            // check if mage/acolyte with three schools/sects.
            string secondarySlug = GuildService.CreateGuildSlug(SecondaryGuild());

            Guild primaryGuild = GuildService.GetGuild(RuleSetID, primarySlug);
            Guild secondaryGuild = GuildService.GetGuild(RuleSetID, secondarySlug);

            if (Class() == "Mage" && secondaryGuild != null && secondaryGuild.PrimaryClass == "Mage")
            {
                return from g in allowedList
                       where g.PrimaryClass == "Mage"
                       select g;
            }

            if (Class() == "Acolyte" && secondaryGuild != null && secondaryGuild.PrimaryClass == "Acolyte")
            {
                return from g in allowedList
                       where g.PrimaryClass == "Acolyte"
                       select g;
            }

            return null;
        }
        #endregion

        #region SetGuild
        /// <summary>
        /// Set guild membership for the character.
        /// To have a primary guild the character must have >= 200 points.
        /// A primary guild must follow the class chosen, i.e. warriors select
        /// from the warrior guilds.
        /// To have a secondary guild they must choose a primary guild and
        /// the secondary guild must be in the allowed guild list.
        /// To have a tertiary guild they must be a scout and have a secondary
        /// guild.
        /// </summary>
        /// <param name="guild">The guild required.</param>
        /// <param name="guildType">The type of guild primary|secondary|tertiary.</param>
        /// <param name="reason">The reason for any failure</param>
        /// <returns></returns>
        public bool SetGuild(Guild guild, string guildType, out string reason)
        {
            reason = string.Empty;

            if (guild == null)
            {
                reason = "Invalid guild.";
                return false;
            }

            if (guildType == "primary")
            {
                int points = PointsAvailable();

                if (points < 200)
                {
                    reason = "A character must have 200 points or greater to select a primary guild.";
                    return false;
                }

                string characterClass = Class();

                if (characterClass != guild.PrimaryClass)
                {
                    reason = string.Format("The guild '{0}' is not a primary guild for your class.", guild.Name);
                    return false;
                }
            }
            else if (guildType == "secondary")
            {
                // check there is a primary guild.
                string primaryGuild = PrimaryGuild();

                if (string.IsNullOrEmpty(primaryGuild))
                {
                    reason = "You must select a primary guild before a secondary guild can be chosen";
                    return false;
                }

                // check for valid secondary guild in rules.
                IEnumerable<Guild> allowedGuilds = GetAllowedGuildList();

                bool allowed = allowedGuilds.Any(g => g.GuildID == guild.GuildID);

                if (!allowed)
                {
                    reason = string.Format("The guild {} is not an allowed secondary guild of {}.", guild.Name, primaryGuild);
                    return false;
                }
            }
            else if (guildType == "tertiary")
            {
                // Check whether a third guild is allowed.
                if (!TertiaryGuildAllowed(out reason))
                {
                    return false;
                }
            }

            // Set guild.
            XElement guildElement = GetGuildElementByType(guildType);

            // get attribute
            XAttribute nameAttribute = guildElement.Attribute("name");

            if (nameAttribute == null)
            {
                nameAttribute = new XAttribute("name", guild.Slug);
                guildElement.Add(nameAttribute);
            }

            nameAttribute.SetValue(guild.Slug);
            return true;
        }
        #endregion

        #region RemoveGuild
        /// <summary>
        /// Remove a guild assignment.
        /// Any following guild assignments are also removed.
        /// </summary>
        /// <param name="guildType">The type of guild to remove.</param>
        public void RemoveGuild(string guildType)
        {
            XElement primaryGuildElement = GetGuildElementByType("primary");
            XElement secondaryGuildElement = GetGuildElementByType("secondary");
            XElement tertiaryGuildElement = GetGuildElementByType("tertiary");

            List<XElement> guildsToClear = new List<XElement>();

            if (guildType == "primary")
            {
                guildsToClear.Add(primaryGuildElement);
                guildsToClear.Add(secondaryGuildElement);
                guildsToClear.Add(tertiaryGuildElement);
            }
            else if (guildType == "secondary")
            {
                guildsToClear.Add(secondaryGuildElement);
                guildsToClear.Add(tertiaryGuildElement);
            }
            else if (guildType == "tertiary")
            {
                guildsToClear.Add(tertiaryGuildElement);
            }

            foreach (XElement guild in guildsToClear)
            {
                XAttribute nameAttribute = guild.Attribute("name");

                if (nameAttribute == null)
                    continue;

                nameAttribute.SetValue("");
            }
        }
        #endregion

        #region GetGuildByType
        /// <summary>
        /// Get a guild record by type.
        /// </summary>
        /// <param name="guildType">The guild type should be either primary|secondary|tertiary.</param>
        /// <returns>The name of the guild.</returns>
        protected string GetGuildByType(string guildType)
        {
            XElement guild = GetGuildElementByType(guildType);

            if (guild == null)
                return string.Empty;

            XAttribute nameAttribute = guild.Attribute("name");

            if (nameAttribute == null)
            {
                nameAttribute = new XAttribute("name", "");
                guild.Add(nameAttribute);
                return string.Empty;
            }

            Guild guildItem = GuildService.GetGuild(RuleSetID, nameAttribute.Value);

            return guildItem != null ? guildItem.Name : string.Empty;
        }
        #endregion

        #region GetGuildElementByType
        /// <summary>
        /// Get the guild element node for a specific guild type.
        /// Ensures that any missing elements are constructed.
        /// </summary>
        /// <param name="guildType"></param>
        /// <returns></returns>
        protected XElement GetGuildElementByType(string guildType)
        {
            XElement guilds = characterData.Descendants()
               .Where(g => g.Name == "guilds")
               .FirstOrDefault();

            if (guilds == null)
            {
                guilds = 
                    new XElement(
                        "guilds",
                        new XElement("guild", new XAttribute("type", "primary"), new XAttribute("name", "")),
                        new XElement("guild", new XAttribute("type", "secondary"), new XAttribute("name", "")),
                        new XElement("guild", new XAttribute("type", "tertiary"), new XAttribute("name", "")));

                XElement hqpad = characterData.Descendants().Where(h => h.Name == "hqpad").First();
                hqpad.Add(guilds);
            }

            // get guild element.
            XElement guildElement =
                guilds.Descendants()
                .Where(g =>
                    g.Name == "guild" &&
                    g.Attribute("type") != null &&
                    g.Attribute("type").Value == guildType)
                .FirstOrDefault();

            if (guildElement == null)
            {
                guildElement = new XElement("guild", new XAttribute("name", ""), new XAttribute("type", guildType));
                guilds.Add(guildElement);
            }

            return guildElement;
        }
        #endregion

        #region GuildLevel
        /// <summary>
        /// Determine the guild level based on rank.
        /// </summary>
        /// <returns>The character guild level.</returns>
        protected int GuildLevel()
        {
            int level = 0;

            int rank = Rank();

            if (rank < 20)
                level = 0;
            else if (rank < 64)
                level = 1;
            else if (rank < 128)
                level = 2;
            else if (rank < 256)
                level = 3;
            else if (rank >= 256)
                level = 4;

            return level;
        }
        #endregion

        /*
         * Bonus handling
         */

        #region GetBonusesByType
        /// <summary>
        /// Get all active bonuses from all sources filtered by type.
        /// </summary>
        /// <param name="type">The bonus type required.</param>
        /// <returns></returns>
        protected IEnumerable<QualityItem> GetBonusesByType(BonusTypes type)
        {
            return Bonuses
                .Where(b => b.Type == type);
        }
        #endregion

        #region GetBonuses
        /// <summary>
        /// Get all active bonuses from all sources.
        /// <list type="bullet">
        ///     <listheader>Sources are:</listheader>
        ///     <item>Skill bonuses</item>
        ///     <item>Active spell bonuses</item>
        ///     <item>Active invocation bonuses</item>
        ///     <item>Ability bonuses</item>
        ///     <item>Item bonuses</item>
        /// </list>
        /// </summary>
        /// <returns></returns>
        protected List<QualityItem> GetBonuses()
        {
            IBonusService bonusService = new BonusService(context);
            List<Quality> allQualities = GetQualityTypeList();

            /*
             *  Skill bonuses.
             */

            // Get list of skills
            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            // refine down to just list of skill slugs
            IEnumerable<string> skillSlugs =
                boughtSkills
                .Where(s => s.Attribute("name") != null)
                .Select(s => s.Attribute("name").Value);

            // bug: missing concept of number of bought ranks here. expand out skill names by multiple of bought and do a join?

            // Get all bonuses for these skills.
            List<SkillBonus> bonuses = bonusService.GetSkillBonuses(RuleSetID, skillSlugs).ToList();

            List<QualityItem> expandedBonuses = new List<QualityItem>();

            foreach (SkillBonus bonus in bonuses)
            {
                if (!IsBonusRequirementMet(bonus))
                    continue;

                // Find skill record.
                XElement skill = boughtSkills.Where(s => s.Attribute("name") != null && s.Attribute("name").Value == bonus.SkillSlug).FirstOrDefault();

                if (skill == null || skill.Attribute("ranks") == null)
                    continue;

                int ranks = 1;
                int.TryParse(skill.Attribute("ranks").Value, out ranks);

                for (int i = 0; i < ranks; i++)
                    expandedBonuses.Add(ParseQualityItem(bonus.Bonus, allQualities));
            }
            
            // todo: get active spell bonuses.

            // todo: get active invocation bonuses.

            /*
             * Get ability bonuses.
             */
            List<XElement> equippedAbilities =
                (from a in GetEquippedAbilities()
                 where a.Element("use") != null &&
                    a.Element("use").Attribute("type") != null &&
                    a.Element("use").Attribute("type").Value == Enum.GetName(typeof(ItemUseType), ItemUseType.AlwaysOn)
                 select a).ToList();

            // extract qualities
            List<QualityItem> abilityQualities = (
                from a in equippedAbilities.Elements("quality")
                select ParseQualityItem(a, allQualities)).ToList();

            expandedBonuses.AddRange(abilityQualities);


            /*
             *  Get armour bonuses.
             */
            List<XElement> equippedArmours = GetEquippedArmours();

            List<QualityItem> armourQualities = (
                from a in equippedArmours.Elements("quality")
                select ParseQualityItem(a, allQualities)).ToList();

            expandedBonuses.AddRange(armourQualities);

            /*
             * Get item bonuses.
             */
            List<XElement> equippedItems = 
                (from a in GetEquippedItems()
                 where a.Element("use") != null &&
                    a.Element("use").Attribute("type") != null &&
                    a.Element("use").Attribute("type").Value == Enum.GetName(typeof(ItemUseType), ItemUseType.AlwaysOn)
                 select a).ToList();

            // extract qualities
            List<QualityItem> itemQualities = (
                from a in equippedItems.Elements("quality")
                select ParseQualityItem(a, allQualities)).ToList();

            expandedBonuses.AddRange(itemQualities);

            return expandedBonuses;
        }
        #endregion

        #region IsBonusRequirementMet
        /// <summary>
        /// Determine if any skill bonus requirement is met.
        /// </summary>
        /// <param name="bonus">The bonus to check.</param>
        /// <returns>Whether the bonus has been met.</returns>
        protected bool IsBonusRequirementMet(SkillBonus bonus)
        {
            if (string.IsNullOrEmpty(bonus.Bonus.Requirement))
                return true;

            BonusRequirement requirementType = (BonusRequirement) Enum.Parse(typeof(BonusRequirement), bonus.Bonus.Requirement);

            bool isValid = false;

            switch (requirementType)
            {
                case BonusRequirement.EquippedArmour:
                {
                    // Get any equipped armour (not headwear or tabards).
                    List<XElement> armours = GetEquippedArmours();

                    XElement firstEquippedPhysicalArmour = armours.Where(a => a.Attribute("type") != null &&
                        (a.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.Metal) ||
                        a.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.NonMetal)))
                        .FirstOrDefault();

                    isValid = (firstEquippedPhysicalArmour != null); // only valid if armour equipped.
                    break;
                }
                default:
                {
                    isValid = true;
                    break;
                }
            }

            return isValid;
        }
        #endregion

        #region ParseQualityItem
        protected QualityItem ParseQualityItem(Bonus bonus, List<Quality> allQualities)
        {
            BonusTypes type = (BonusTypes) Enum.Parse(typeof(BonusTypes), bonus.Type);
            string value = !string.IsNullOrEmpty(bonus.Value) ? bonus.Value : string.Empty;

            // get description.
            Quality qual = allQualities.Where(a => a.Type == type).FirstOrDefault();

            string description = string.Format(qual.Format, bonus.Quantity, value);
            
            QualityItem quality = new QualityItem()
            {
                Quantity = bonus.Quantity,
                Type = type,
                Value = value,
                Description = description,
                ParentDescription = bonus.Skill.Name
            };

            return quality;
        }
        #endregion

        /*
         *  Skill requirement evaluation.
         */

        #region EvaluateRequirements
        /// <summary>
        /// Evaluate whether a skill's requirements have been met.
        /// </summary>
        /// <param name="skill"></param>
        /// <returns></returns>
        protected bool EvaluateRequirements(Skill skill, bool passGuildCheck)
        {
            // todo: check for an AllowSkill bonus which allows the skill in any circumstances.

            // check guild requirements first.  Any guild requirement can be met.
            bool guildCheck = false;

            if (passGuildCheck)
                guildCheck = true;
            else
            {
                IEnumerable<SkillRequirement> guildRequirements = skill.Requirements.Where(r => r.GuildID.HasValue);

                if (guildRequirements == null || guildRequirements.Count() == 0) // no guild requirements
                    guildCheck = true;
                else
                {
                    string primaryGuildName = PrimaryGuild();
                    int guildLevel = GuildLevel();

                    Guild primaryGuild = GuildService.GetGuild(RuleSetID, GuildService.CreateGuildSlug(primaryGuildName));

                    foreach (SkillRequirement req in guildRequirements)
                    {
                        bool ok = EvaluateGuildRequirement(req, primaryGuild, guildLevel);

                        if (ok)
                        {
                            guildCheck = true;
                            break;
                        }
                    }
                }
            }

            // check any other requirements. Requirements are either AND's or OR's.
            // All AND's must be met but any OR can be met.

            // Process AND requirements.
            IEnumerable<SkillRequirement> andRequirements = 
                skill.Requirements
                .Where(r => r.Combination == Enum.GetName(typeof(RequirementCombination), RequirementCombination.AND) && !r.GuildID.HasValue);

            bool andCheck = false;

            if (andRequirements == null || andRequirements.Count() == 0)
                andCheck = true;
            {
                // Check each one.
                bool allTrue = true;

                foreach (SkillRequirement req in andRequirements)
                {
                    if (req.RequiredSkillID.HasValue) // skill requirement.
                    {
                        if (!EvaluateSkillRequirement(req))
                        {
                            allTrue = false;
                            break;
                        }
                    }
                    else if (req.MinRank.HasValue) // rank requirement.
                    {
                        if (!EvaluateMinRankRequirement(req))
                        {
                            allTrue = false;
                            break;
                        }
                    }
                }

                andCheck = allTrue;
            }

            bool orCheck = false;

            IEnumerable<SkillRequirement> orRequirements = 
                skill.Requirements
                .Where(r => r.Combination == Enum.GetName(typeof(RequirementCombination), RequirementCombination.OR) && !r.GuildID.HasValue);

            if (orRequirements == null || orRequirements.Count() == 0)
                orCheck = true;
            {
                // Check each one.
                foreach (SkillRequirement req in orRequirements)
                {
                    if (req.RequiredSkillID.HasValue) // skill requirement.
                    {
                        if (!EvaluateSkillRequirement(req))
                        {
                            orCheck = true;
                            break;
                        }
                    }
                    else if (req.MinRank.HasValue) // rank requirement.
                    {
                        if (!EvaluateMinRankRequirement(req))
                        {
                            orCheck = true;
                            break;
                        }
                    }
                }
            }

            return (guildCheck && andCheck && orCheck);
        }
        #endregion

        #region EvaluateMinRankRequirement
        /// <summary>
        /// Evaluate whether a minimum rank requirement has been met.
        /// </summary>
        /// <param name="requirement">The requirementto meet.</param>
        /// <returns></returns>
        protected bool EvaluateMinRankRequirement(SkillRequirement requirement)
        {
            if (!requirement.MinRank.HasValue)
                return false;

            return Rank() >= requirement.MinRank.Value;
        }
        #endregion

        #region EvaluateSkillRequirement
        /// <summary>
        /// Evaluate whether a skill requirement has been met.
        /// </summary>
        /// <param name="requirement">The requirement to test.</param>
        /// <returns></returns>
        protected bool EvaluateSkillRequirement(SkillRequirement requirement)
        {
            if (!requirement.RequiredSkillID.HasValue)
                return false;

            // Get skill element for required skill slug.
            string skillSlug = requirement.RequiredSkill.Slug;

            XElement skill = GetBoughtSkills()
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == skillSlug)
                .FirstOrDefault();

            if (skill == null)
                return false;

            if (!requirement.MinRank.HasValue) // no min number of ranks.
                return true;

            // get ranks bought.
            XAttribute ranks = skill.Attribute("ranks");
            
            int ranksBought = 0;

            if (ranks == null && !int.TryParse(ranks.Value, out ranksBought))
                return false;

            if (ranksBought >= requirement.MinRank.Value)
                return true;

            return false;
        }
        #endregion

        #region EvaluateGuildRequirement
        /// <summary>
        /// Evaluate whether a guild requirement has been met.
        /// </summary>
        /// <param name="requirement"></param>
        /// <param name="primaryGuild"></param>
        /// <param name="guildLevel"></param>
        /// <returns></returns>
        protected bool EvaluateGuildRequirement(SkillRequirement requirement, Guild primaryGuild, int guildLevel)
        {
            // check if primary guild requirement.
            bool primaryOk = (
                !requirement.PrimaryGuildOnly.HasValue ||
                (requirement.PrimaryGuildOnly.Value &&
                 requirement.GuildID.HasValue && 
                 requirement.GuildID.Value == primaryGuild.GuildID));

            // check rank in guild (level).
            bool rankOk = (!requirement.GuildLevel.HasValue || (requirement.GuildLevel.Value <= guildLevel));


            return rankOk && primaryOk;
        }
        #endregion

        /*
         * Adventure records.
         */

        #region GetAdventureRecords
        /// <summary>
        /// Get all the current existing adventure records.
        /// </summary>
        /// <returns></returns>
        public List<RecordItem> GetAdventureRecords()
        {
            List<RecordItem> items = new List<RecordItem>();

            System.Globalization.CultureInfo cInfo= new System.Globalization.CultureInfo("en-GB", true);

            // Get all adventure records.
            IEnumerable<XElement> records =
                characterData.Descendants("records")
                .Descendants("record")
                .OrderBy(r => DateTime.ParseExact(r.Attribute("date").Value, DateFormatStringForXml, cInfo));

            foreach (XElement record in records)
            {
                DateTime date = DateTime.MinValue;
                string description = "unknown";
                int points = 0;
                int id = 0;

                XAttribute dateAttribute = record.Attribute("date");

                if (dateAttribute != null && !string.IsNullOrEmpty(dateAttribute.Value))
                {
                    DateTime.TryParse(dateAttribute.Value, out date);
                }

                XAttribute descAttribute = record.Attribute("name");

                if (descAttribute != null && !string.IsNullOrEmpty(descAttribute.Value))
                    description = descAttribute.Value;

                XAttribute pointsAttribute = record.Attribute("points");

                if (pointsAttribute != null && !string.IsNullOrEmpty(pointsAttribute.Value))
                {
                    int.TryParse(pointsAttribute.Value, out points);
                }

                XAttribute idAttribute = record.Attribute("id");

                if (idAttribute != null && !string.IsNullOrEmpty(idAttribute.Value))
                {
                    int.TryParse(idAttribute.Value, out id);
                }

                items.Add(new RecordItem()
                {
                    Date = date,
                    Description = description,
                    Points = points,
                    ID = id
                });
            }

            return items;
        }
        #endregion

        #region AddAdventureRecord
        /// <summary>
        /// Add a new adventure record.
        /// </summary>
        /// <param name="date">The event date.</param>
        /// <param name="description">The optional description</param>
        /// <param name="points">The points awarded.</param>
        public void AddAdventureRecord(DateTime date, string description, int points, int? deaths, AttendanceType type, string eventType)
        {
            XElement records = characterData.Descendants("records").FirstOrDefault();
            
            if (records == null)
            {
                records = new XElement("records");

                XElement root = characterData.Element("hqpad");

                if (root == null)
                    throw new ApplicationException("The character XML is invalid.  Now root node found.");

                root.Add(records);
            }

            // Get max id from records.
            int maxId = 0;

            List<int> recordList = 
                records.Elements("record")
                .Where(e => e.Attribute("id") != null && !string.IsNullOrEmpty(e.Attribute("id").Value))
                .Select(e => Convert.ToInt32(e.Attribute("id").Value)).ToList();

            if (recordList != null && recordList.Count > 0)
                maxId = recordList.Max();

            XElement record = new XElement("record",
                    new XAttribute("date", date.ToString(DateFormatStringForXml)),
                    new XAttribute("name", description),
                    new XAttribute("id", maxId + 1),
                    new XAttribute("points", points),
                    new XAttribute("attendance", Enum.GetName(typeof(AttendanceType), type)));

            record.Add(new XAttribute("deaths", deaths.HasValue ? deaths.Value.ToString() : "0"));

            if (!string.IsNullOrEmpty(eventType))
                record.Add(new XAttribute("type", eventType));

            records.Add(record);

            // Calculate the points total.
            CalculatePointsStatusAndRank();
        }
        #endregion

        #region UpdateAdventureRecord
        /// <summary>
        /// Update an adventure record item.
        /// </summary>
        /// <param name="itemId">The id of the item.</param>
        /// <param name="date">The event date.</param>
        /// <param name="eventName">The event name.</param>
        /// <param name="points">The number of points from the event.</param>
        public void UpdateAdventureRecord(int itemId, DateTime date, string eventName, int points)
        {
            XElement record = GetAdventureRecordByID(itemId);

            XAttribute nameAttribute = record.Attribute("name");

            if (nameAttribute == null)
            {
                nameAttribute = new XAttribute("name", eventName);
                record.Add(nameAttribute);
            }
            else
                nameAttribute.SetValue(eventName);

            XAttribute dateAttribute = record.Attribute("date");
            if (dateAttribute == null)
            {
                dateAttribute = new XAttribute("date", date.ToString(DateFormatStringForXml));
                record.Add(dateAttribute);
            }
            else
                dateAttribute.SetValue(date.ToString(DateFormatStringForXml));

            XAttribute pointsAttribute = record.Attribute("points");
            if (nameAttribute == null)
            {
                pointsAttribute = new XAttribute("points", points);
                record.Add(pointsAttribute);
            }
            else
                pointsAttribute.SetValue(points);

            // Calculate the points total.
            CalculatePointsStatusAndRank();
        }
        #endregion

        #region RemoveAdventureRecord
        /// <summary>
        /// Remove an adventure record element.
        /// </summary>
        /// <param name="id"></param>
        public void RemoveAdventureRecord(int id)
        {
            XElement record = GetAdventureRecordByID(id);

            record.Remove();

            // Calculate the points total.
            CalculatePointsStatusAndRank();
        }
        #endregion

        #region GetAdventureRecordByID
        /// <summary>
        /// Get an adventure record by it's id.
        /// </summary>
        /// <param name="id">An adventure record id.</param>
        /// <returns></returns>
        protected XElement GetAdventureRecordByID (int id)
        {
            XElement record = 
                characterData.Descendants("record")
                .Where(e => e.Attribute("id") != null && !string.IsNullOrEmpty(e.Attribute("id").Value) && Convert.ToInt32(e.Attribute("id").Value) == id)
                .FirstOrDefault();  
      
            if (record == null)
                throw new ApplicationException(string.Format("No adventure record with id {0} was found.", id));

            return record;
        }
        #endregion


        /*
         * Points and skill costs.
         */

        #region CalculatePointsStatusAndRank
        /// <summary>
        /// Calculate the character points, status and rank.
        /// </summary>
        protected void CalculatePointsStatusAndRank()
        {
            int pointsTotal = 0;

            foreach (XElement record in characterData.Descendants("records").Descendants("record"))
            {
                XAttribute pointsAttribute = record.Attribute("points");

                if (pointsAttribute != null && !string.IsNullOrEmpty(pointsAttribute.Value))
                {
                    int pointValue = 0;
                    if (int.TryParse(pointsAttribute.Value, out pointValue))
                        pointsTotal += pointValue;
                }
            }

            // record points in character element.
            XElement characterElement = GetCharacterElement();
            XAttribute pointsAttr = characterElement.Attribute("points");

            if (pointsAttr != null)
                pointsAttr.SetValue(pointsTotal);

            // Get effective points and calculate rank and status.
            int effectivePoints = EffectivePoints();

            int status = (pointsTotal > 200) ? (pointsTotal / 100) - 1 : 0;
            int rank = (pointsTotal / 10);

            XAttribute rankAttribute = characterElement.Attribute("rank");

            if (rankAttribute == null)
                characterElement.Add(new XAttribute("rank", rank));
            else
                rankAttribute.SetValue(rank);

            XAttribute statusAttribute = characterElement.Attribute("status");

            if (statusAttribute == null)
                characterElement.Add(new XAttribute("status", status));
            else
                statusAttribute.SetValue(status);
        }
        #endregion

        #region CalculateSkillCost
        /// <summary>
        /// Calculate the cost of a skill given a number of ranks.
        /// </summary>
        /// <param name="skill">A skill with costs.</param>
        /// <param name="ranks">The number of ranks bought in the skill.</param>
        /// <param name="status">The amount of status cost for the skill.</param>
        /// <returns>The points cost of the skill.</returns>
        protected int CalculateSkillCost(SkillView skill, int ranks, out int status)
        {
            SkillCost cost = skillService.GetSkillCosts(skill.SkillID).Where(c => c.PointsTableID == Table.PointsTableID).FirstOrDefault();

            if (cost == null)
                throw new ApplicationException(string.Format("Unable to find points costs for skill {0}.", skill.Name));

            int total = 0;

            if (cost.Factor.HasValue && cost.Factor.Value > 0) // series form of (R+C)*F or form of R*F (where C is implicitly 0).
            {
                int C = (cost.Cost.HasValue) ? cost.Cost.Value : 0;

                for (int i = 1; i <= ranks; i++)
                {

                    total += C + (i * cost.Factor.Value);
                }
            }
            else if (cost.Cost.HasValue) // form of Ranks*C
            {
                total += ranks * cost.Cost.Value;
            }

            // calculate total status required.
            status = (skill.Status.HasValue) ? skill.Status.Value * ranks : 0;

            return total;
        }

        protected int CalculateSkillCost(Skill skill, int ranks, out int status)
        {
            SkillCost cost = skill.Costs.Where(c => c.PointsTableID == Table.PointsTableID).FirstOrDefault();

            if (cost == null)
                throw new ApplicationException(string.Format("Unable to find points costs for skill {0}.", skill.Name));

            int total = 0;

            if (cost.Factor.HasValue && cost.Factor.Value > 0) // series form of (R+C)*F or form of R*F (where C is implicitly 0).
            {
                int C = (cost.Cost.HasValue) ? cost.Cost.Value : 0;

                for (int i = 1; i <= ranks; i++)
                {

                    total += C + (i * cost.Factor.Value);
                }
            }
            else if (cost.Cost.HasValue) // form of Ranks*C
            {
                total += ranks * cost.Cost.Value;
            }

            // calculate total status required.
            status = (skill.Status.HasValue) ? skill.Status.Value * ranks : 0;

            return total;
        }
        #endregion

        #region BuildCostString
        /// <summary>
        /// Create a descriptive cost string for a skill.
        /// </summary>
        /// <param name="skill"></param>
        /// <returns></returns>
        protected string BuildCostString(SkillView skill)
        {
            PointsTable table = RulesService.GetPointsTable(Class(), Race());

            if (table == null)
                throw new ApplicationException(string.Format("Unable to find points table for {1} {0}.", Class(), Race()));

            SkillCost cost = skillService.GetSkillCosts(skill.SkillID).Where(c => c.PointsTableID == table.PointsTableID).FirstOrDefault();

            if (cost == null)
                throw new ApplicationException(string.Format("Unable to find points costs for skill {0}.", skill.Name));

            string costString = "unknown";

            if (cost.Factor.HasValue && cost.Factor.Value > 0) // series form of (R+C)*F or form of R*F (where C is implicitly 0).
            {
                if (!cost.Cost.HasValue || cost.Cost.Value == 0)
                {
                    if (cost.Factor > 1)
                        costString = "Rank * " + cost.Factor.Value.ToString();
                    else
                        costString = "Rank";
                }
                else
                {
                    if (cost.Factor > 1)
                        costString = string.Format("{1} + Rank * {0}", cost.Factor.Value, cost.Cost.Value);
                    else
                        costString = string.Format("Rank + {0}", cost.Cost.Value);
                }
            }
            else if (cost.Cost.HasValue) // form of Ranks*C
            {
                costString = string.Format("{0}", cost.Cost.Value);
            }

            return costString;
        }
        #endregion

        #region CalculatePointsSpent
        /// <summary>
        /// Calculate the total skill point and status cost for all skills.
        /// Record the totals
        /// </summary>
        protected void CalculatePointsSpent()
        {
            int pointsSpent = 0;
            int statusSpent = 0;

            // cycle all skills and recalculate thier cost.
            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            foreach (XElement boughtSkill in boughtSkills)
            {
                XAttribute nameAttribute = boughtSkill.Attribute("name");
                XAttribute ranksAttribute = boughtSkill.Attribute("ranks");

                int ranks = 0;

                if (nameAttribute == null || string.IsNullOrEmpty(nameAttribute.Value) ||
                    ranksAttribute == null || !int.TryParse(ranksAttribute.Value, out ranks))
                {
                    // bad skill record.
                    continue;
                }

                // get skill.
                Skill skill = SkillService.GetSkill(RuleSetID, nameAttribute.Value);

                if (skill == null)
                    throw new ApplicationException(string.Format("Invalid skill {0}", nameAttribute.Value));

                // calculate points and status.
                int status = 0;

                int cost = CalculateSkillCost(skill, ranks, out status);

                pointsSpent += cost;
                statusSpent += status;
            }

            // record totals.
            XElement characterElement = GetCharacterElement();

            XAttribute pointsAttribute = characterElement.Attribute("spentpoints");
            if (pointsAttribute == null)
            {
                pointsAttribute = new XAttribute("spentpoints", pointsSpent);
                characterElement.Add(pointsAttribute);
            }
            else
                pointsAttribute.SetValue(pointsSpent);

            XAttribute statusAttribute = characterElement.Attribute("spentstatus");
            if (statusAttribute == null)
            {
                statusAttribute = new XAttribute("spentstatus", statusSpent);
                characterElement.Add(statusAttribute);
            }
            else
                statusAttribute.SetValue(statusSpent);
        }
        #endregion

        /*
         *  Life calculation.
         */

        #region GetLifeRecord
        /// <summary>
        /// Get the total and locational life values.
        /// </summary>
        /// <returns>Total life record.</returns>
        public LifeRecord GetLifeRecord()
        {
            // Basic life values.
            int totalLife = 42;
            int locationLife = 14;

            // Get all life bonuses.
            IEnumerable<QualityItem> bonuses = GetBonusesByType(BonusTypes.LifeRank);

            int totalRanks = bonuses.Sum(b => b.Quantity);
            totalLife += totalRanks * 3;
            locationLife += totalRanks;

            LifeRecord model = new LifeRecord()
            {
                TotalLife = totalLife,
                LocationalLife = locationLife
            };

            return model;
        }
        #endregion

        /*
         *  AC calculations
         */

        #region GetArmourClasses
        /// <summary>
        /// Get the set of AC's for the character.
        /// AC's are derived from the character bonuses.
        /// </summary>
        /// <returns></returns>
        public List<ACRecord> GetArmourClasses()
        {
            List<ACRecord> records = new List<ACRecord>();

            // Check whether un or armoured dex bought.
            int unarmouredDex = GetBonusesByType(BonusTypes.UnarmouredDex).Select(b => b.Quantity).Sum();
            int armouredDex = GetBonusesByType(BonusTypes.ArmouredDex).Select(b => b.Quantity).Sum();

            ACRecord physical = new ACRecord() { Name = "Physical" };
            ACRecord power = new ACRecord() { Name = "Power" };
            ACRecord magical = new ACRecord() { Name = "Magical" };

            // Get any equipped armour records.
            List<XElement> equippedArmours = GetEquippedArmours();

            // find any metal or non-metal equipped armours.
            XElement equippedPhysicalArmour = (from a in equippedArmours
                                               where a.Attribute("type") != null &&
                                               (((ArmourType) Enum.Parse(typeof(ArmourType), a.Attribute("type").Value)) == ArmourType.Metal ||
                                                ((ArmourType) Enum.Parse(typeof(ArmourType), a.Attribute("type").Value)) == ArmourType.NonMetal)
                                               select a).FirstOrDefault();

            var tabardsAndHeadwear = from a in equippedArmours
                                     where a.Attribute("type") != null &&
                                     (((ArmourType)Enum.Parse(typeof(ArmourType), a.Attribute("type").Value)) == ArmourType.Tabard ||
                                     ((ArmourType)Enum.Parse(typeof(ArmourType), a.Attribute("type").Value)) == ArmourType.Headwear)
                                     select a;

            // Get any pure phyiscal ac.

            // Equipped armour physical ac.
            bool armourEquipped = false;
            if (equippedPhysicalArmour != null)
            {
                armourEquipped = true;
                ArmourType armourType = (ArmourType) Enum.Parse(typeof(ArmourType), equippedPhysicalArmour.Attribute("type").Value);
                string customName = equippedPhysicalArmour.Attribute("name") != null ? equippedPhysicalArmour.Attribute("name").Value : string.Empty;

                string armourName = "Metal armour";
                int ac = 8;

                if (armourType == ArmourType.NonMetal)
                {
                    armourName = "Non-metal armour";
                    ac = 6;
                }

                int superior = 0;

                if (int.TryParse(equippedPhysicalArmour.Attribute("superior").Value, out superior))
                {
                    ac += superior;
                    armourName = string.Format("{0} +{1}", armourName, superior);
                }

                if (!string.IsNullOrEmpty(customName))
                    armourName = customName;

                physical.Pure += ac;
                physical.Sources.Add(new ACSource() { Name = armourName, Value = ac });

                // Get any pure magical or pure power ac from equipped armour.
                int enscorcelled = 0;
                int empowered = 0;

                if (int.TryParse(equippedPhysicalArmour.Attribute("enscorcelled").Value, out enscorcelled) && enscorcelled > 0)
                {
                    magical.Pure += enscorcelled;
                    magical.Sources.Add(new ACSource() { Name = armourName, Value = enscorcelled });
                }

                if (int.TryParse(equippedPhysicalArmour.Attribute("empowered").Value, out empowered) && empowered > 0)
                {
                    power.Pure += empowered;
                    power.Sources.Add(new ACSource() { Name = armourName, Value = empowered });
                }
            }

            // get any physical AC bonuses.
            foreach (QualityItem bonus in GetBonusesByType(BonusTypes.PhysicalAC))
            {
                string name = (!string.IsNullOrEmpty(bonus.ParentDescription)) ? bonus.ParentDescription : (!string.IsNullOrEmpty(bonus.Description)) ? bonus.Description : string.Empty;

                physical.Pure += bonus.Quantity;
                physical.Sources.Add(new ACSource() { Name = name, Value = bonus.Quantity, Parent = string.Empty });
            }

            // If no armour equipped and unarmoured dex > armoured then use that instead.
            if (!armourEquipped && unarmouredDex > 0 && unarmouredDex > armouredDex)
            {
                physical.Sources.Add(new ACSource() { Name = "Unamoured Dex", Value = unarmouredDex });
                physical.Total = physical.Pure + unarmouredDex;
            }
            else
            {
                physical.Total = physical.Pure + armouredDex;

                if (armouredDex > 0)
                {
                    physical.Sources.Add(new ACSource() { Name = "Armoured Dex", Value = armouredDex });
                }
            }

            records.Add(physical);

            // Do the same for any tabards or headear that are equipped.
            foreach (XElement armourItem in tabardsAndHeadwear)
            {
                ArmourType armourType = (ArmourType)Enum.Parse(typeof(ArmourType), armourItem.Attribute("type").Value);

                string name = "Tabard";

                if (armourType == ArmourType.Headwear)
                    name = "Headwear";

                int enscorcelled = 0;

                if (int.TryParse(armourItem.Attribute("enscorcelled").Value, out enscorcelled) && enscorcelled > 0)
                {
                    magical.Pure += enscorcelled;
                    magical.Sources.Add(new ACSource() { Name = name, Value = enscorcelled });
                }

                int empowered = 0;

                if (int.TryParse(armourItem.Attribute("empowered").Value, out empowered) && empowered > 0)
                {
                    power.Pure += empowered;
                    power.Sources.Add(new ACSource() { Name = name, Value = empowered });
                }
            }

            // Bonus related magical ac.
            foreach (QualityItem bonus in GetBonusesByType(BonusTypes.MagicalAC))
            {
                string name = (!string.IsNullOrEmpty(bonus.ParentDescription)) ? bonus.ParentDescription : (!string.IsNullOrEmpty(bonus.Description)) ? bonus.Description : string.Empty;

                magical.Pure += bonus.Quantity;
                magical.Sources.Add(new ACSource() { Name = name, Value = bonus.Quantity, Parent = string.Empty });
            }

            // If no armour equipped and unarmoured dex > armoured then use that instead.
            if (!armourEquipped && unarmouredDex > 0 && unarmouredDex > armouredDex)
            {
                magical.Sources.Add(new ACSource() { Name = "Unarmoured Dex", Value = unarmouredDex });
                magical.Total = magical.Pure + unarmouredDex;
            }
            else
            {
                magical.Total = magical.Pure + armouredDex;
                if (armouredDex > 0)
                {
                    magical.Sources.Add(new ACSource() { Name = "Armoured Dex", Value = armouredDex });
                }
            }
            records.Add(magical);

            // Get any pure power ac.

            // Bonus related power ac.
            foreach (QualityItem bonus in GetBonusesByType(BonusTypes.PowerAC))
            {
                string name = (!string.IsNullOrEmpty(bonus.ParentDescription)) ? bonus.ParentDescription : (!string.IsNullOrEmpty(bonus.Description)) ? bonus.Description : string.Empty;

                power.Pure += bonus.Quantity;
                power.Sources.Add(new ACSource() { Name = name, Value = bonus.Quantity, Parent = string.Empty });
            }

            // If no armour equipped and unarmoured dex > armoured then use that instead.
            if (!armourEquipped && unarmouredDex > 0 && unarmouredDex > armouredDex)
            {
                power.Sources.Add(new ACSource() { Name = "Unarmoured Dex", Value = unarmouredDex });
                power.Total = power.Pure + unarmouredDex;
            }
            else
            {
                power.Total = power.Pure + armouredDex;
                if (armouredDex > 0)
                {
                    power.Sources.Add(new ACSource() { Name = "Armoured Dex", Value = armouredDex });
                }
            }

            records.Add(power);

            // Add a pure dex AC record.
            if (armourEquipped && armouredDex > 0)
            {
                ACRecord dex= new ACRecord() { Name = "Dex", Pure = armouredDex, Total = armouredDex };
                dex.Sources.Add(new ACSource() { Name = "Armoured Dex", Value = armouredDex });
                records.Add(dex);
            }
            else if (!armourEquipped && unarmouredDex > 0)
            {
                ACRecord dex = new ACRecord() { Name = "Dex", Pure = unarmouredDex, Total = unarmouredDex };
                dex.Sources.Add(new ACSource() { Name = "Unarmoured Dex", Value = unarmouredDex });
                records.Add(dex);
            }

            return records;
        }
        #endregion

        /*
         *  Magic and Schools of magic.
         */

        #region SaveSchoolOptions
        /// <summary>
        /// Update the character with magic school options.
        /// </summary>
        /// <param name="options">A model containing the school options selected.</param>
        public void SaveSchoolOptions(List<SchoolAccessModelItem> options)
        {
            XElement magicElement = GetMagicElement();

            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            // Process each school selection provided in turn.
            foreach (SchoolAccessModelItem item in options)
            {
                if (item.AccessSlug == "school-1" || item.AccessSlug == "school-2")
                {
                    // update the staring school choices in the magic element.
                    XAttribute school = magicElement.Attribute(item.AccessSlug);

                    if (school == null) // add missing attribute.
                    {
                        school = new XAttribute(item.AccessSlug, item.SchoolSlug);
                        magicElement.Add(school);
                    }

                    school.SetValue(item.SchoolSlug);

                    continue;
                }

                // A skill option choice so update the relevant skill element.
                XElement skill = boughtSkills
                    .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == item.AccessSlug)
                    .FirstOrDefault();

                if (skill == null)
                    continue;

                #region Swap specialist spells if necessary
                // Update any specialist spell system allocation if changing between two schools of magic.
                if (skill.Attribute("name").Value == "make-focus")
                {
                    string previousGuild = skill.Attribute("selected-value") != null ? skill.Attribute("selected-value").Value : string.Empty;
                    string newGuild = item.SchoolSlug;

                    Guild schoolGuild = null;

                    if (!string.IsNullOrEmpty(previousGuild))
                    {
                        // remove specialist spell for colour.
                        schoolGuild = GuildService.GetGuild(RuleSetID, previousGuild);

                        if (schoolGuild.PrimaryClass == "Mage" && !string.IsNullOrEmpty(schoolGuild.MagicColour))
                        {
                            // Get specialist spell for colour.
                            string specialistSpell =
                                (from s in SpellService.GetSpells(RuleSetID)
                                 where s.Colour == schoolGuild.MagicColour && s.IsSpecialist == true
                                 select s.GroupSlug)
                                    .FirstOrDefault();

                            // remove specialist spell.
                            UpdateSpell(specialistSpell, false, false, true);
                        }
                    }

                    schoolGuild = GuildService.GetGuild(RuleSetID, newGuild);

                    if (schoolGuild.PrimaryClass == "Mage" && !string.IsNullOrEmpty(schoolGuild.MagicColour))
                    {
                        // Get specialist spell for colour.
                        string specialistSpell =
                            (from s in SpellService.GetSpells(RuleSetID)
                             where s.Colour == schoolGuild.MagicColour && s.IsSpecialist == true
                             select s.GroupSlug)
                                .FirstOrDefault();

                        // remove specialist spell.
                        UpdateSpell(specialistSpell, true, false, true);
                    }
                }
                #endregion

                // get selected-value attribute and add if missing.
                XAttribute selected = skill.Attribute("selected-value");

                if (selected == null)
                {
                    selected = new XAttribute("selected-value", item.SchoolSlug);
                    skill.Add(selected);
                }

                selected.SetValue(item.SchoolSlug);
            }
        }
        #endregion

        #region GetSchoolAccessOptions
        /// <summary>
        /// Build a list of school access options (or choices).
        /// The basic options are the two starting schools.
        /// Buying the extra colour skills allows further mage access.
        /// Buying a focus allows wizard access.
        /// Buying the wizard in 2nd and 3rd colour allows wizard access.
        /// Buying the Sorceror (and 2nd sorc.) skills allow sorceror access.
        /// A bonus can give mage, wizard or sorceror access to specific schools (but no choice).
        /// </summary>
        /// <returns></returns>
        public List<SchoolAccessModelItem> GetSchoolAccessOptions()
        {
            List<SchoolAccessModelItem> options = new List<SchoolAccessModelItem>();

            // Get existing school records from the magic section of the character.
            XElement magicElement = GetMagicElement();

            string school1 = (magicElement.Attribute("school-1") != null && !string.IsNullOrEmpty(magicElement.Attribute("school-1").Value)) ? magicElement.Attribute("school-1").Value : string.Empty;
            string school2 = (magicElement.Attribute("school-2") != null && !string.IsNullOrEmpty(magicElement.Attribute("school-2").Value)) ? magicElement.Attribute("school-2").Value : string.Empty;

            // Get any access assignment to the "basic-1" colour (mage access)
            options.Add(new SchoolAccessModelItem()
            {
                AccessSlug = "school-1",
                AccessType = "Mage",
                AccessName = "Basic school 1",
                SchoolSlug = school1
            });

            // Get any access assignment to the "basic-2" colour (mage access)
            options.Add(new SchoolAccessModelItem()
            {
                AccessSlug = "school-2",
                AccessType = "Mage",
                AccessName = "Basic school 2",
                SchoolSlug = school2
            });

            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            List<string[]> magicSkills = new List<string[]>()
            {
                new string[2] {"extra-colour-3rd", "Mage"},
                new string[2] {"extra-colour-4th", "Mage"},
                new string[2] {"extra-colour-5th", "Mage"},
                new string[2] {"extra-colour-6th", "Mage"},
                new string[2] {"extra-colour-7th", "Mage"},
                new string[2] {"extra-colour-8th", "Mage"},
                new string[2] {"make-focus", "Wizard"},
                new string[2] {"wizard-in-2nd-colour", "Wizard"},
                new string[2] {"wizard-in-3rd-colour", "Wizard"},
                new string[2] {"sorceror-in-primary-colour", "Sorceror"},
                new string[2] {"sorceror-in-2nd-colour", "Sorceror"}
            };

            // Check each magical skill to see if they have been bought.
            // If so then extract an existing school choice and add them to the choice list.
            foreach (string[] pair in magicSkills)
            {
                // Check for bought skill.
                XElement skill = boughtSkills
                    .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == pair[0])
                    .FirstOrDefault();

                if (skill == null)
                    continue;

                // get selected school for this skill.
                string school = (skill.Attribute("selected-value") != null && !string.IsNullOrEmpty(skill.Attribute("selected-value").Value)) ? skill.Attribute("selected-value").Value : string.Empty;

                // Get skill display name.
                Skill skillData = SkillService.GetSkill(RuleSetID, skill.Attribute("name").Value);

                options.Add(new SchoolAccessModelItem()
                {
                    AccessSlug = skill.Attribute("name").Value,
                    AccessName = skillData.Name,
                    AccessType = pair[1],
                    SchoolSlug = school
                });
            }

            return options;
        }
        #endregion

        #region GetSchoolAccess
        /// <summary>
        /// Get character access levels to schools of magic.
        /// </summary>
        /// <returns></returns>
        public List<SchoolAccessRecord> GetSchoolAccess()
        {
            List<SchoolAccessRecord> records = new List<SchoolAccessRecord>();
            
            // Get existing school records from the magic section of the character.
            XElement magicElement = GetMagicElement();

            string school1 = (magicElement.Attribute("school-1") != null && !string.IsNullOrEmpty(magicElement.Attribute("school-1").Value)) ? magicElement.Attribute("school-1").Value : string.Empty;
            string school2 = (magicElement.Attribute("school-2") != null && !string.IsNullOrEmpty(magicElement.Attribute("school-2").Value)) ? magicElement.Attribute("school-2").Value : string.Empty;

            if (!string.IsNullOrEmpty(school1))
            {
                Guild guild = GuildService.GetGuild(RuleSetID, school1);

                if (guild != null)
                {
                    // Add mage access to stsrting school.
                    records.Add(new SchoolAccessRecord() { Access = "Mage", SchoolName = guild.Name, SourceSlug = guild.Slug });
                }
            }

            if (!string.IsNullOrEmpty(school2))
            {
                Guild guild = GuildService.GetGuild(RuleSetID, school2);

                if (guild != null)
                {
                    // Add mage access to stsrting school.
                    records.Add(new SchoolAccessRecord() { Access = "Mage", SchoolName = guild.Name, SourceSlug = guild.Slug });
                }
            }

            // Now each bought skill.
            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            List<string[]> magicSkills = new List<string[]>()
            {
                new string[2] {"extra-colour-3rd", "Mage"},
                new string[2] {"extra-colour-4th", "Mage"},
                new string[2] {"extra-colour-5th", "Mage"},
                new string[2] {"extra-colour-6th", "Mage"},
                new string[2] {"extra-colour-7th", "Mage"},
                new string[2] {"extra-colour-8th", "Mage"},
                new string[2] {"make-focus", "Wizard"},
                new string[2] {"wizard-in-2nd-colour", "Wizard"},
                new string[2] {"wizard-in-3rd-colour", "Wizard"},
                new string[2] {"sorceror-in-primary-colour", "Sorceror"},
                new string[2] {"sorceror-in-2nd-colour", "Sorceror"}
            };

            // Check each magical skill to see if they have been bought.
            // If so then extract an existing school choice and add them to the choice list.
            foreach (string[] pair in magicSkills)
            {
                // Check for bought skill.
                XElement skill = boughtSkills
                    .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == pair[0])
                    .FirstOrDefault();

                if (skill == null)
                    continue;

                // get selected school for this skill.
                string school = (skill.Attribute("selected-value") != null && !string.IsNullOrEmpty(skill.Attribute("selected-value").Value)) ? skill.Attribute("selected-value").Value : string.Empty;

                if (!string.IsNullOrEmpty(school))
                {
                    // Get skill display name.
                    Guild guild = GuildService.GetGuild(RuleSetID, school);

                    bool specialist = false;
                    bool primary = false;

                    // Check for default specialist access as extra to school level access.
                    if (skill.Attribute("name").Value == "make-focus")
                    {
                        //records.Add(new SchoolAccessRecord() { SchoolName = guild.Name, SourceSlug = guild.Slug, Access = "Specialist" });
                        specialist = true;
                        primary = true;
                    }

                    // get any existing record.
                    SchoolAccessRecord existingRecord = records.Where(r => r.SourceSlug == school).FirstOrDefault();

                    if (existingRecord == null)
                    {
                        existingRecord = new SchoolAccessRecord() { SchoolName = guild.Name, SourceSlug = guild.Slug, SpecialistAllowed = specialist, PrimaryAllowed = primary };
                        records.Add(existingRecord);
                    }

                    if (string.IsNullOrEmpty(existingRecord.Access))
                        existingRecord.Access = pair[1];
                    else if (pair[1] == "Wizard" && existingRecord.Access == "Mage")
                        existingRecord.Access = pair[1];
                    else if (pair[1] == "Sorceror" && (existingRecord.Access == "Mage" || existingRecord.Access == "Wizard"))
                        existingRecord.Access = pair[1];

                    if (specialist)
                        existingRecord.SpecialistAllowed = specialist;

                    if (primary)
                        existingRecord.PrimaryAllowed = primary;
                }
            }

            // todo: Check for bonuses that give  Mage, Wizard or Sorceror access to schools of magic.
            List<QualityItem> bonusList = GetBonuses();
            List<QualityItem> accessBonuses = new List<QualityItem>();

            // Check for mage access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SchoolAccessMage));

            // Check for wizard access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SchoolAccessWizard));

            // Check for sorceror access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SchoolAccessSorceror));

            // Check for specialist access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SchoolAccessSpecialist));

            // Check for specialist access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SchoolAccessPrimary));

            foreach (QualityItem bonus in accessBonuses)
            {
                string schoolSlug = bonus.Value;
                string accessType =
                    bonus.Type == BonusTypes.SchoolAccessSorceror ? "Sorceror" :
                    bonus.Type == BonusTypes.SchoolAccessWizard ? "Wizard" : "Mage";

                // Get skill display name.
                Guild bonusGuild = GuildService.GetGuild(RuleSetID, schoolSlug);

                SchoolAccessRecord existingRecord = null;

                existingRecord = records.Where(r => r.SourceSlug == schoolSlug).FirstOrDefault();

                bool specialist = (bonus.Type == BonusTypes.SchoolAccessSpecialist);
                bool primary = (bonus.Type == BonusTypes.SchoolAccessPrimary);


                // get any existing record.
                existingRecord = records.Where(r => r.SourceSlug == schoolSlug).FirstOrDefault();

                if (existingRecord == null)
                {
                    existingRecord = new SchoolAccessRecord() { SchoolName = bonusGuild.Name, SourceSlug = bonusGuild.Slug, SpecialistAllowed = specialist, PrimaryAllowed = primary };
                    records.Add(existingRecord);
                }

                if (string.IsNullOrEmpty(existingRecord.Access))
                    existingRecord.Access = accessType;
                else if (accessType == "Wizard" && existingRecord.Access == "Mage")
                    existingRecord.Access = accessType;
                else if (accessType == "Sorceror" && (existingRecord.Access == "Mage" || existingRecord.Access == "Wizard"))
                    existingRecord.Access = accessType;

                if (specialist)
                    existingRecord.SpecialistAllowed = specialist;

                if (primary)
                    existingRecord.PrimaryAllowed = primary;
            }

            return records;
        }
        #endregion

        #region SaveSchoolStatus
        /// <summary>Save the selected status allocations to the schools of magic.</summary>
        /// <param name="status">The status allocations to save.</param>
        public void SaveSchoolStatus(SchoolStatus status)
        {
            XElement magicElement = GetMagicElement();

            var groups =
                (from a in status.Status
                 group a by a.SchoolSlug into grouped
                 select new { SchoolSlug = grouped.Key, Items = grouped });

            // Loop by school group and process each level type in turn.
            foreach (var item in groups)
            {
                string slug = item.SchoolSlug;

                int allocation1to8 = item.Items.Where(a => a.Type == SchoolStatusAssignmentType.Level1to8).Sum(b => b.Status);
                int allocation9 = item.Items.Where(a => a.Type == SchoolStatusAssignmentType.Level9).Sum(b => b.Status);
                int allocation10 = item.Items.Where(a => a.Type == SchoolStatusAssignmentType.Level10).Sum(b => b.Status);

                // Get school element from magic element
                XElement school = magicElement.Elements("school").Where(s => s.Attribute("school-slug") != null && s.Attribute("school-slug").Value == slug).FirstOrDefault();

                if (school == null)
                {
                    school = new XElement("school", new XAttribute("school-slug", slug));
                    magicElement.Add(school);
                }

                // Get 1-8 attribute.
                XAttribute level1to8 = school.Attribute("level1to8");

                if (level1to8 == null)
                {
                    level1to8 = new XAttribute("level1to8", allocation1to8.ToString());
                    school.Add(level1to8);
                }
                else
                    level1to8.SetValue(allocation1to8.ToString());

                // Get 9 attribute.
                XAttribute level9 = school.Attribute("level9");

                if (level9 == null)
                {
                    level9 = new XAttribute("level9", allocation9.ToString());
                    school.Add(level9);
                }
                else
                    level9.SetValue(allocation9.ToString());

                // Get 10 attribute.
                XAttribute level10 = school.Attribute("level10");

                if (level10 == null)
                {
                    level10 = new XAttribute("level10", allocation9.ToString());
                    school.Add(level10);
                }
                else
                    level10.SetValue(allocation10.ToString());
            }
        }
        #endregion

        #region GetSchoolStatus
        /// <summary>
        /// Get a list of school/status spent combinations.
        /// </summary>
        /// <returns></returns>
        public SchoolStatus GetSchoolStatus()
        {
            SchoolStatus characterStatus = new SchoolStatus();

            List<SchoolStatusAssignment> statuses = new List<SchoolStatusAssignment>();

            XElement magicElement = GetMagicElement();

            // Get each school element and grab status assigned.
            foreach (XElement schoolElement in magicElement.Elements("school"))
            {
                // Get school slug
                string slug = (schoolElement.Attribute("school-slug") != null && !string.IsNullOrEmpty(schoolElement.Attribute("school-slug").Value)) ? schoolElement.Attribute("school-slug").Value : string.Empty;
                string level1to8String = (schoolElement.Attribute("level1to8") != null && !string.IsNullOrEmpty(schoolElement.Attribute("level1to8").Value)) ? schoolElement.Attribute("level1to8").Value : string.Empty;
                string level9String = (schoolElement.Attribute("level9") != null && !string.IsNullOrEmpty(schoolElement.Attribute("level9").Value)) ? schoolElement.Attribute("level9").Value : string.Empty;
                string level10String = (schoolElement.Attribute("level10") != null && !string.IsNullOrEmpty(schoolElement.Attribute("level10").Value)) ? schoolElement.Attribute("level10").Value : string.Empty;

                string colour = ConvertSchoolToColour(slug);

                int status1to8 = 0;
                int status9 = 0;
                int status10 = 0;

                int.TryParse(level1to8String, out status1to8);
                
                Guild guild = GuildService.GetGuild(RuleSetID, slug);
                statuses.Add(new SchoolStatusAssignment() { SchoolSlug = slug, Status = status1to8, SchoolName = guild.Name, Type = SchoolStatusAssignmentType.Level1to8, SpellsAvailable = status1to8 * SpellsPerStatusFactor, Colour = colour });

                int.TryParse(level9String, out status9);
                
                guild = GuildService.GetGuild(RuleSetID, slug);
                statuses.Add(new SchoolStatusAssignment() { SchoolSlug = slug, Status = status9, SchoolName = guild.Name, Type = SchoolStatusAssignmentType.Level9, SpellsAvailable = status9 * SpellsPerStatusFactor, Colour = colour });


                int.TryParse(level10String, out status10);
                
                guild = GuildService.GetGuild(RuleSetID, slug);
                statuses.Add(new SchoolStatusAssignment() { SchoolSlug = slug, Status = status10, SchoolName = guild.Name, Type = SchoolStatusAssignmentType.Level10, SpellsAvailable = status10 * SpellsPerStatusFactor, Colour = colour });
            }

            // Get total status spend on level 1 to 8 spells.
            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            XElement skill1to8 = boughtSkills
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == "learn-level-18-spells")
                .FirstOrDefault();

            SchoolStatusTotal total1to8 = new SchoolStatusTotal() { Type = SchoolStatusAssignmentType.Level1to8, Total = 0, SpellsAvailable = 0 };

            int statusTotal1to8 = 0;

            if (skill1to8 != null && skill1to8.Attribute("ranks") != null && int.TryParse(skill1to8.Attribute("ranks").Value, out statusTotal1to8))
            {
                total1to8.Total = statusTotal1to8;
                total1to8.SpellsAvailable = statusTotal1to8 * SpellsPerStatusFactor;
            }

            characterStatus.Totals.Add(total1to8);

            // Get total status spend on level 9 spells.
            XElement skill9 = boughtSkills
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == "learn-level-9-spells")
                .FirstOrDefault();

            SchoolStatusTotal total9 = new SchoolStatusTotal() { Type = SchoolStatusAssignmentType.Level9, Total = 0 };

            int statusTotal9 = 0;

            if (skill9 != null && skill9.Attribute("ranks") != null && int.TryParse(skill9.Attribute("ranks").Value, out statusTotal9))
            {
                total9.Total = statusTotal9;
                total9.SpellsAvailable = statusTotal9 * SpellsPerStatusFactor;
            }

            characterStatus.Totals.Add(total9);

            // Get total status spend on level 10 spells.
            XElement skill10 = boughtSkills
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == "learn-level-10-spells")
                .FirstOrDefault();

            SchoolStatusTotal total10 = new SchoolStatusTotal() { Type = SchoolStatusAssignmentType.Level10, Total = 0 };

            int statusTotal10 = 0;

            if (skill10 != null && skill10.Attribute("ranks") != null && int.TryParse(skill10.Attribute("ranks").Value, out statusTotal10))
            {
                total10.Total = statusTotal10;
                total10.SpellsAvailable = statusTotal10 * SpellsPerStatusFactor;
            }

            characterStatus.Totals.Add(total10);


            //// Get the school access records where any access is wizard or sorceror, and it is not already in the list, add a status record.
            //List<SchoolAccessRecord> access = GetSchoolAccess();

            //foreach (SchoolAccessRecord record in access.Where(a => a.Access == "Wizard" || a.Access == "Sorceror"))
            //{
            //    // Check if school already exists in status list.
            //    SchoolStatusAssignment statusItem = statuses.Where(s => s.SchoolSlug == record.SourceSlug).FirstOrDefault();
            //    string colour = ConvertSchoolToColour(record.SourceSlug);

            //    if (statusItem == null)
            //    {
            //        statuses.Add(new SchoolStatusAssignment() { SchoolSlug = record.SourceSlug, SchoolName = record.SchoolName, Status = 0, Type = SchoolStatusAssignmentType.Level1to8, Colour = colour });
            //        statuses.Add(new SchoolStatusAssignment() { SchoolSlug = record.SourceSlug, SchoolName = record.SchoolName, Status = 0, Type = SchoolStatusAssignmentType.Level9, Colour = colour });
            //        statuses.Add(new SchoolStatusAssignment() { SchoolSlug = record.SourceSlug, SchoolName = record.SchoolName, Status = 0, Type = SchoolStatusAssignmentType.Level10, Colour = colour });
            //    }
            //}

            characterStatus.Status.AddRange(statuses);

            return characterStatus;
        }
        #endregion

        #region GetMagicElement
        /// <summary>
        /// Get the magic element from the character data.
        /// If it doesn't exist then add it into the document structure.
        /// </summary>
        /// <returns></returns>
        public XElement GetMagicElement()
        {
            XElement characterElement = GetCharacterElement();

            XElement magicElement = characterElement.Descendants("magic").FirstOrDefault();

            if (magicElement == null)
            {
                magicElement = new XElement("magic");
                characterElement.Add(magicElement);
            }

            return magicElement;
        }
        #endregion

        #region GetAvailableSpells
        /// <summary>
        /// Get all the available spells for a character.
        /// Any existing spell choices are always included, even if they are invalid.
        /// All spells are included that are not common spells and thier requirements are met.
        /// </summary>
        /// <returns></returns>
        public List<SpellItem> GetAvailableSpells()
        {
            List<XElement> knownSpellList = GetKnownSpells();

            // Get existing spell choices.
            string knownSpells = knownSpellList != null ? 
                string.Join(",", (knownSpellList.Select(s => s.Attribute("name").Value).ToArray())) :
                string.Empty;

            // Get allowed school access.
            List<SchoolAccessRecord> access = GetSchoolAccess();

            StringBuilder colours = new StringBuilder();
            StringBuilder colourLevels = new StringBuilder();
            StringBuilder primaryColours = new StringBuilder();
            StringBuilder specialistColours = new StringBuilder();

            int characterRank = Rank();

            foreach (SchoolAccessRecord record in access)
            {
                //string formatString = string.Format("{0}_", ConvertSchoolToColour(record.SourceSlug));

                string colour = ConvertSchoolToColour(record.SourceSlug);
                string level = "8";

                if (record.Access == "Sorceror" || record.Access == "Wizard")
                {
                    if (record.Access == "Sorceror")
                    {
                        level = (characterRank >= 256) ? "10" : "9";
                    }

                    if (colours.Length > 0)
                    {
                        colours.Append(",");
                        colourLevels.Append(",");
                    }

                    colours.Append(colour);
                    colourLevels.Append(level);
                }

                if (record.PrimaryAllowed)
                {
                    if (primaryColours.Length > 0)
                        primaryColours.Append(",");

                    primaryColours.Append(colour);
                }

                if (record.SpecialistAllowed)
                {
                    if (specialistColours.Length > 0)
                        specialistColours.Append(",");

                    specialistColours.Append(colour);
                }
            }

            IEnumerable<Spell> allowedSpells =
                SpellService.GetAllowedSpells(
                    RuleSetID,
                    knownSpells,
                    colours.ToString(),
                    colourLevels.ToString(),
                    primaryColours.ToString(),
                    specialistColours.ToString());

            List<SpellItem> spells = new List<SpellItem>();

            // Group spells in the same spell group into one lot with a level range (e.g. 1-5).
            var spellGroups =
                (from s in allowedSpells
                 group s by s.GroupSlug into spellGroup
                 select new { 
                     GroupSlug = spellGroup.Key, 
                     Item = spellGroup.OrderBy(a => a.Level).Last(),
                     Level = 
                        (spellGroup.Count() > 1) ?
                            spellGroup.OrderBy(a => a.Level).First().Level.ToString() + "-" + spellGroup.OrderBy(a => a.Level).Last().Level.ToString() :
                            spellGroup.First().Level.ToString()
                 });

            foreach (var spell in spellGroups)
            {
                // Get known spell if present.
                XElement spellElement = knownSpellList != null ?
                    knownSpellList.Where(s => s.Attribute("name") != null && s.Attribute("name").Value == spell.GroupSlug).FirstOrDefault() :
                    null;

                bool selected = (spellElement != null && spellElement.Attribute("selected") != null && spellElement.Attribute("selected").Value == "true");
                bool attuned = (spellElement != null && spellElement.Attribute("attuned") != null && spellElement.Attribute("attuned").Value == "true");
                bool system = (spellElement != null && spellElement.Attribute("system") != null && spellElement.Attribute("system").Value == "true");

                SchoolStatusAssignmentType assignmentType =
                    (spell.Item.Level == 10) ? SchoolStatusAssignmentType.Level10 :
                    (spell.Item.Level == 9) ? SchoolStatusAssignmentType.Level9 :
                    SchoolStatusAssignmentType.Level1to8;

                spells.Add(
                    new SpellItem() {
                        Colour = spell.Item.Colour,
                        Group = spell.GroupSlug,
                        Family = spell.Item.Family,
                        Level = spell.Level,
                        Name = spell.Item.Name,
                        Selected = selected,
                        Attuned = attuned,
                        SystemSpell = system,
                        AssignmentType = assignmentType
                    });
            }

            return spells;
        }
        #endregion

        #region ConvertSchoolToColour
        /// <summary>
        /// Convert a school guild slug into the associated colour of magic.
        /// </summary>
        /// <param name="slug"></param>
        /// <returns></returns>
        protected string ConvertSchoolToColour(string slug)
        {
            string colour = "Grey";

            Guild school = GuildService.GetGuild(RuleSetID, slug);

            if (school != null && !string.IsNullOrEmpty(school.MagicColour))
                colour = school.MagicColour;

            return colour;
        }
        #endregion

        #region GetKnownSpells
        /// <summary>
        /// Get all the spells known by the character.
        /// </summary>
        /// <returns>The list of known spells.</returns>
        public List<XElement> GetKnownSpells()
        {
            if (boughtSpells == null)
            {
                XElement magic = GetMagicElement();

                boughtSpells = magic.Descendants("spell")
                    .Where(a => a.Attribute("selected") != null && Convert.ToBoolean(a.Attribute("selected").Value))
                    .ToList();
            }

            return boughtSpells;
        }
        #endregion

        #region RefreshKnownSpells
        /// <summary>
        /// Ensure that the next list of known spells is fresh.
        /// </summary>
        protected void RefreshKnownSpells()
        {
            boughtSpells = null;
        }
        #endregion

        #region UpdateSpell
                /// <summary>
        /// Set whether a spell is selected or attuned.
        /// A spell that is not selected cannot be attuned to either.
        /// </summary>
        /// <param name="groupName">The spell group name.</param>
        /// <param name="selected">Whether the character has selected the spell.</param>
        /// <param name="attuned">Whether the character is attuned to the spell.</param>
        public void UpdateSpell(string groupName, bool selected, bool attuned)
        {
            UpdateSpell(groupName, selected, attuned, false);
        }

        /// <summary>
        /// Set whether a spell is selected or attuned.
        /// A spell that is not selected cannot be attuned to either.
        /// </summary>
        /// <param name="groupName">The spell group name.</param>
        /// <param name="selected">Whether the character has selected the spell.</param>
        /// <param name="attuned">Whether the character is attuned to the spell.</param>
        /// <param name="system">Whether the spell is added by the system (and therefore can't be removed otherwise).</param>
        public void UpdateSpell(string groupName, bool selected, bool attuned, bool system)
        {
            List<XElement> knownSpellList = GetKnownSpells();

            XElement spell = knownSpellList.Where(a => a.Attribute("name") != null && a.Attribute("name").Value == groupName).FirstOrDefault();

            if (spell == null)
            {
                spell = new XElement("spell", new XAttribute("name", groupName));
                XElement magicElement = GetMagicElement();
                magicElement.Add(spell);
            }

            // update selected value
            XAttribute selectedAttibute = spell.Attribute("selected");

            if (selectedAttibute == null)
                spell.Add(new XAttribute("selected", selected.ToString().ToLower()));
            else
                selectedAttibute.SetValue(selected.ToString().ToLower());

            // update attuned value
            if (!selected)
                attuned = false; // can't attune to unselected spell.

            XAttribute attunedAttibute = spell.Attribute("attuned");

            if (attunedAttibute == null)
                spell.Add(new XAttribute("attuned", attuned.ToString().ToLower()));
            else
                attunedAttibute.SetValue(attuned.ToString().ToLower());

            XAttribute systemAttibute = spell.Attribute("system");

            if (systemAttibute == null)
                spell.Add(new XAttribute("system", system.ToString().ToLower()));
            //else
            //    systemAttibute.SetValue(system.ToString().ToLower());

            if (!selected)
                UnselectRequiredSpell(groupName);

            RefreshKnownSpells();
        }
        #endregion

        #region UnselectRequiredSpell
        /// <summary>
        /// For a given spell group name, get any spells chosen that are dependent upon 
        /// it and remove those also.
        /// </summary>
        /// <param name="groupName">The spell group name unselected.</param>
        protected void UnselectRequiredSpell(string groupName)
        {
            var spells = SpellService.GetSpellsByRequiredGroup(RuleSetID, groupName).ToList();

            List<XElement> knownSpells = GetKnownSpells();

            foreach (Spell spellToRevoke in spells)
            {
                XElement spell = knownSpells != null ? knownSpells.Where(a => a.Attribute("name") != null && a.Attribute("name").Value == spellToRevoke.GroupSlug).FirstOrDefault() : null;

                if (spell != null)
                {
                    XAttribute selectedAttibute = spell.Attribute("selected");

                    if (selectedAttibute == null)
                        spell.Add(new XAttribute("selected", "false"));
                    else
                        selectedAttibute.SetValue("false");

                    // process any items dependent on this invocation.
                    UnselectRequiredSpell(spellToRevoke.GroupSlug);
                }
            }
        }
        #endregion

        #region GetSpellSlots
        /// <summary>
        /// Create a list of all spells available per level.
        /// </summary>
        /// <returns></returns>
        protected List<SpellLevelSlot> GetSpellSlots()
        {
            List<SpellLevelSlot> slots = new List<SpellLevelSlot>();

            // get all spell slot skills bought.

            for (int level = 1; level <= 10; level++)
            {
                string skillSlug = string.Format("spell-slot-level-{0}", level);
                XElement spellSkill = GetBoughtSkills().Where(a => a.Attribute("name") != null && a.Attribute("name").Value == skillSlug).FirstOrDefault();

                // Get ranks (slots) bought.
                int ranks = 0;

                if (spellSkill == null || spellSkill.Attribute("ranks") == null || !int.TryParse(spellSkill.Attribute("ranks").Value, out ranks))
                    continue;

                slots.Add(new SpellLevelSlot() { Level = level, Spells = ranks });
            }

            return slots;
        }
        #endregion

        #region GetSpellsAllotment
        /// <summary>
        /// Get known/learnt spell allotments.
        /// </summary>
        /// <returns></returns>
        public List<LearntSpellsAllotment> GetSpellsAllotment()
        {
            List<LearntSpellsAllotment> allotments = new List<LearntSpellsAllotment>();

            var boughtSkills = GetBoughtSkills();
            List<SpellItem> availableSpells = GetAvailableSpells();
            
            // level 1 to 8 allotment.
            allotments.Add(CreateAllotment(3, availableSpells, SchoolStatusAssignmentType.Level1to8, BonusTypes.Spells1to8));

            // level 9 allotment.
            allotments.Add(CreateAllotment(2, availableSpells, SchoolStatusAssignmentType.Level9, BonusTypes.Spells9));

            // level 10 allotment.
            allotments.Add(CreateAllotment(2, availableSpells, SchoolStatusAssignmentType.Level10, BonusTypes.Spells10));

            return allotments;
        }

        protected LearntSpellsAllotment CreateAllotment(int spellMultiplier, List<SpellItem> availableSpells, SchoolStatusAssignmentType levelType, BonusTypes bonusType)
        {
            //var skill = boughtSkills
            //     .Where(a =>
            //         a.Attribute("name") != null &&
            //         a.Attribute("ranks") != null &&
            //         a.Attribute("name").Value == SkillSlugs.LearnLevel1To8Spells &&
            //         a.Attribute("ranks").Value != "0")
            //     .FirstOrDefault();

            //// get ranks.
            //int ranks = (skill != null) ? Convert.ToInt32(skill.Attribute("ranks").Value) : 0;
            //int total = ranks * spellMultiplier;
            int total = 0;

            var bonuses = GetBonusesByType(bonusType);

            foreach (var bonus in bonuses)
                total += bonus.Quantity * spellMultiplier;

            // now work out how many have been spent on spells.
            int learntSpells = 0;

            foreach (var spell in GetKnownSpells())
            {
                // check if is level by looking at avialable spells for this group.
                var available = availableSpells.Where(b => b.AssignmentType == levelType && !b.SystemSpell && b.Group == spell.Attribute("name").Value).FirstOrDefault();

                if (available == null)
                    continue;

                learntSpells++;
            }

            return new LearntSpellsAllotment()
                {
                    Level = levelType,
                    AvailableToLearn = total,
                    Learnt = learntSpells
                };
        }
        #endregion

        /*
         *  Power and sect access.
         */
        #region GetSectAccess
        /// <summary>
        /// Get character access levels to sects of power.
        /// </summary>
        /// <returns></returns>
        public List<SectAccessRecord> GetSectAccess()
        {
            List<SectAccessRecord> records = new List<SectAccessRecord>();

            // Get existing sect records from the power section of the character.
            XElement powerElement = GetPowerElement();

            // Now each bought skill.
            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            List<string[]> powerSkills = new List<string[]>()
            {
                new string[3] {"make-talisman", "Priest", "FirstGuild"},
                new string[3] {"priest-in-2nd-sphere", "Priest", "SecondGuild"},
                new string[3] {"priest-in-3rd-sphere", "Priest", "ThirdGuild"},
                new string[3] {"high-priest-in-1st-sphere", "High Priest", "FirstGuild"},
                new string[3] {"specialist-sect-access-(acolyte)", "Lay", "FreeChoice"},
                new string[3] {"specialist-sect-access-(priest)", "Priest", "FreeChoice"}
            };

            Guild primaryGuild = GuildService.GetGuild(RuleSetID, GuildService.CreateGuildSlug(PrimaryGuild()));
            Guild secondaryGuild = GuildService.GetGuild(RuleSetID, GuildService.CreateGuildSlug(SecondaryGuild()));
            Guild tertiaryGuild = GuildService.GetGuild(RuleSetID, GuildService.CreateGuildSlug(TertiaryGuild()));

            List<Guild> guildList = new List<Guild>();

            // Add Lay access to any acolyte guilds.
            if (primaryGuild != null && primaryGuild.PrimaryClass == "Acolyte")
            {
                guildList.Add(primaryGuild);
                records.Add(new SectAccessRecord() { SectName = primaryGuild.Name, SourceSlug = primaryGuild.Slug, Access = "Lay" });
            }

            if (secondaryGuild != null && secondaryGuild.PrimaryClass == "Acolyte")
            {
                guildList.Add(secondaryGuild);
                records.Add(new SectAccessRecord() { SectName = secondaryGuild.Name, SourceSlug = secondaryGuild.Slug, Access = "Lay" });
            }

            if (tertiaryGuild != null && tertiaryGuild.PrimaryClass == "Acolyte")
            {
                guildList.Add(tertiaryGuild);
                records.Add(new SectAccessRecord() { SectName = tertiaryGuild.Name, SourceSlug = tertiaryGuild.Slug, Access = "Lay" });
            }

            // Check each power skill to see if they have been bought.
            // If so then extract an existing sect choice and add them to the choice list.
            // certain skills only reflect the characters guild choices.
            foreach (string[] pair in powerSkills)
            {
                // Check for bought skill.
                XElement skill = boughtSkills
                    .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == pair[0])
                    .FirstOrDefault();

                if (skill == null)
                    continue;

                Guild itemGuild = null;
                string sect = string.Empty;

                if (pair[2] == "FirstGuild") // If fixed to primary guild.
                {
                    if (guildList.Count < 1)
                        continue;

                    itemGuild = guildList[0];
                    sect = guildList[0].Slug;
                }
                else if (pair[2] == "SecondGuild") // If fixed to secondary guild.
                {
                    if (guildList.Count < 2)
                        continue;

                    itemGuild = guildList[1];
                    sect = guildList[1].Slug;
                }
                else if (pair[2] == "ThirdGuild") // If fixed to secondary guild.
                {
                    if (guildList.Count < 3)
                        continue;

                    itemGuild = guildList[2];
                    sect = guildList[2].Slug;
                }
                else
                {
                    // try and get selected value from skill.
                    sect = (skill.Attribute("selected-value") != null && !string.IsNullOrEmpty(skill.Attribute("selected-value").Value)) ? skill.Attribute("selected-value").Value : string.Empty;
                    Guild guild = GuildService.GetGuild(RuleSetID, sect);
                }

                if (!string.IsNullOrEmpty(sect))
                {
                    // get any existing record.
                    SectAccessRecord existingRecord = records.Where(r => r.SourceSlug == sect).FirstOrDefault();

                    if (existingRecord == null)
                    {
                        existingRecord = new SectAccessRecord() { SectName = itemGuild.Name, SourceSlug = itemGuild.Slug };
                        records.Add(existingRecord);
                    }

                    if (string.IsNullOrEmpty(existingRecord.Access))
                        existingRecord.Access = pair[1];
                    else if (pair[1] == "Priest" && existingRecord.Access == "Lay")
                        existingRecord.Access = pair[1];
                    else if (pair[1] == "High Priest" && (existingRecord.Access == "Lay" || existingRecord.Access == "Priest"))
                        existingRecord.Access = pair[1];
                }
            }

            // todo: Check for bonuses that give  Mage, Wizard or Sorceror access to schools of magic.
            List<QualityItem> bonusList = GetBonuses();
            List<QualityItem> accessBonuses = new List<QualityItem>();

            // Check for mage access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SectAccessLay));

            // Check for wizard access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SectAccessPriest));

            // Check for sorceror access bonuses.
            accessBonuses.AddRange(
                bonusList.Where(b => b.Type == BonusTypes.SectAccessHighPriest));

            foreach (QualityItem bonus in accessBonuses)
            {
                string schoolSlug = bonus.Value;
                string accessType =
                    bonus.Type == BonusTypes.SectAccessHighPriest ? "High Priest" :
                    bonus.Type == BonusTypes.SectAccessPriest ? "Priest" : "Lay";

                // Get skill display name.
                Guild bonusGuild = GuildService.GetGuild(RuleSetID, schoolSlug);

                SectAccessRecord existingRecord = null;

                // get any existing record.
                existingRecord = records.Where(r => r.SourceSlug == schoolSlug).FirstOrDefault();

                if (existingRecord == null)
                {
                    existingRecord = new SectAccessRecord() { SectName = bonusGuild.Name, SourceSlug = bonusGuild.Slug };
                    records.Add(existingRecord);
                }

                if (string.IsNullOrEmpty(existingRecord.Access))
                    existingRecord.Access = accessType;
                else if (accessType == "Priest" && existingRecord.Access == "Lay")
                    existingRecord.Access = accessType;
                else if (accessType == "High Priest" && (existingRecord.Access == "Lay" || existingRecord.Access == "Priest"))
                    existingRecord.Access = accessType;
            }

            return records;
        }
        #endregion

        #region GetPowerElement
        /// <summary>
        /// Get the power element from the character data.
        /// If it doesn't exist then add it into the document structure.
        /// </summary>
        /// <returns></returns>
        public XElement GetPowerElement()
        {
            XElement characterElement = GetCharacterElement();

            XElement powerElement = characterElement.Descendants("power").FirstOrDefault();

            if (powerElement == null)
            {
                powerElement = new XElement("power");
                characterElement.Add(powerElement);
            }

            return powerElement;
        }
        #endregion

        #region GetSectStatus
        /// <summary>
        /// Get a list of sect/status spent combinations.
        /// </summary>
        /// <returns></returns>
        public SectStatus GetSectStatus()
        {
            SectStatus characterStatus = new SectStatus();

            List<SectStatusAssignment> statuses = new List<SectStatusAssignment>();

            XElement powerElement = GetPowerElement();

            int invocationsPerStatusfactor = InvocationsPerStatusFactor;

            List<string> sects = new List<string>(); // maintain a list of sects processed.

            // Determine if primary or secondary power caster.
            if (PrimaryGuild().Length > 0)
            {
                string primarySlug = guildService.CreateGuildSlug(PrimaryGuild());
                Guild guild = GuildService.GetGuild(RuleSetID, primarySlug);

                if (guild.PrimaryClass != "Acolyte")
                    invocationsPerStatusfactor = InvocationsPerStatusFactorSecondary; // primary guild not Acolyte so must be a secondary caster.
            }

            // Get each sect element and grab status assigned.
            foreach (XElement sectElement in powerElement.Elements("sect"))
            {
                // Get school slug
                string slug = (sectElement.Attribute("sect-slug") != null && !string.IsNullOrEmpty(sectElement.Attribute("sect-slug").Value)) ? sectElement.Attribute("sect-slug").Value : string.Empty;
                string level1to8String = (sectElement.Attribute("level1to8") != null && !string.IsNullOrEmpty(sectElement.Attribute("level1to8").Value)) ? sectElement.Attribute("level1to8").Value : string.Empty;
                string level9String = (sectElement.Attribute("level9") != null && !string.IsNullOrEmpty(sectElement.Attribute("level9").Value)) ? sectElement.Attribute("level9").Value : string.Empty;
                string level10String = (sectElement.Attribute("level10") != null && !string.IsNullOrEmpty(sectElement.Attribute("level10").Value)) ? sectElement.Attribute("level10").Value : string.Empty;

                sects.Add(slug);

                int status1to8 = 0;
                int status9 = 0;
                int status10 = 0;

                int.TryParse(level1to8String, out status1to8);

                Guild guild = GuildService.GetGuild(RuleSetID, slug);
                statuses.Add(new SectStatusAssignment() { SectSlug = slug, Status = status1to8, SectName = guild.Name, Type = SectStatusAssignmentType.Level1to8, InvocationsAvailable = status1to8 * invocationsPerStatusfactor });

                int.TryParse(level9String, out status9);

                guild = GuildService.GetGuild(RuleSetID, slug);
                statuses.Add(new SectStatusAssignment() { SectSlug = slug, Status = status9, SectName = guild.Name, Type = SectStatusAssignmentType.Level9, InvocationsAvailable = status9 * CosmicInvocationsPerStatusFactor });


                int.TryParse(level10String, out status10);

                guild = GuildService.GetGuild(RuleSetID, slug);
                statuses.Add(new SectStatusAssignment() { SectSlug = slug, Status = status10, SectName = guild.Name, Type = SectStatusAssignmentType.Level10, InvocationsAvailable = status10 * CosmicInvocationsPerStatusFactor });
            }

            // Ensure that all Acolyte guild assignments have a sect element.
            if (PrimaryGuild().Length > 0)
            {
                string primarySlug = guildService.CreateGuildSlug(PrimaryGuild());
                Guild guild = GuildService.GetGuild(RuleSetID, primarySlug);

                if (guild.PrimaryClass == "Acolyte" && !sects.Contains(primarySlug))
                {
                    statuses.Add(new SectStatusAssignment() { SectSlug = primarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level1to8, InvocationsAvailable = 0 });
                    statuses.Add(new SectStatusAssignment() { SectSlug = primarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level9, InvocationsAvailable = 0 });
                    statuses.Add(new SectStatusAssignment() { SectSlug = primarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level10, InvocationsAvailable = 0 });
                }
            }

            if (SecondaryGuild().Length > 0)
            {
                string secondarySlug = guildService.CreateGuildSlug(SecondaryGuild());
                Guild guild = GuildService.GetGuild(RuleSetID, secondarySlug);

                if (guild.PrimaryClass == "Acolyte" && !sects.Contains(secondarySlug))
                {
                    statuses.Add(new SectStatusAssignment() { SectSlug = secondarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level1to8, InvocationsAvailable = 0 });
                    statuses.Add(new SectStatusAssignment() { SectSlug = secondarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level9, InvocationsAvailable = 0 });
                    statuses.Add(new SectStatusAssignment() { SectSlug = secondarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level10, InvocationsAvailable = 0 });
                }
            }

            if (TertiaryGuild().Length > 0)
            {
                string tertiarySlug = guildService.CreateGuildSlug(TertiaryGuild());
                Guild guild = GuildService.GetGuild(RuleSetID, tertiarySlug);

                if (guild.PrimaryClass == "Acolyte" && !sects.Contains(tertiarySlug))
                {
                    statuses.Add(new SectStatusAssignment() { SectSlug = tertiarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level1to8, InvocationsAvailable = 0 });
                    statuses.Add(new SectStatusAssignment() { SectSlug = tertiarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level9, InvocationsAvailable = 0 });
                    statuses.Add(new SectStatusAssignment() { SectSlug = tertiarySlug, Status = 0, SectName = guild.Name, Type = SectStatusAssignmentType.Level10, InvocationsAvailable = 0 });
                }
            }

            // Get all learnt invocations so we get amount used per band.
            List<XElement> knownInvocations = GetKnownInvocations();

            int learnt1to8 = 0;
            int learnt9 = 0;
            int learnt10 = 0;

            foreach (XElement invocation in knownInvocations)
            {
                // get invocation name
                string name = invocation.Attribute("name").Value;
                string selectedAsString = invocation.Attribute("selected").Value;

                if (!selectedAsString.ToLowerInvariant().Equals("true"))
                    continue;

                // get invocation record so we can work out what band it is in.
                var invocationList = InvocationService.GetInvocationsByGroup(RuleSetID, name).ToList();

                if (invocationList == null || invocationList.Count() == 0)
                    continue;

                // get highest invocation in group to determine band.
                int maxRank = invocationList.Max(a => a.Rank);

                if (maxRank <= 8)
                    learnt1to8++;
                if (maxRank == 9)
                    learnt9++;
                else if (maxRank == 10)
                    learnt10++;
            }

            // Get total status spend on level 1 to 8 spells.
            IEnumerable<XElement> boughtSkills = GetBoughtSkills();

            XElement skill1to8 = boughtSkills
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == "learn-rank-18-invocations")
                .FirstOrDefault();

            SectStatusTotal total1to8 = new SectStatusTotal() { Type = SectStatusAssignmentType.Level1to8, Total = 0, InvocationsAvailable = 0 };

            int statusTotal1to8 = 0;

            if (skill1to8 != null && skill1to8.Attribute("ranks") != null && int.TryParse(skill1to8.Attribute("ranks").Value, out statusTotal1to8))
            {
                total1to8.Total = statusTotal1to8;
                total1to8.InvocationsAvailable = statusTotal1to8 * invocationsPerStatusfactor;
                total1to8.InvocationsLearnt = learnt1to8;
            }

            characterStatus.Totals.Add(total1to8);

            // Get total status spend on level 9 spells.
            XElement skill9 = boughtSkills
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == "learn-rank-9-invocations")
                .FirstOrDefault();

            SectStatusTotal total9 = new SectStatusTotal() { Type = SectStatusAssignmentType.Level9, Total = 0 };

            int statusTotal9 = 0;

            if (skill9 != null && skill9.Attribute("ranks") != null && int.TryParse(skill9.Attribute("ranks").Value, out statusTotal9))
            {
                total9.Total = statusTotal9;
                total9.InvocationsAvailable = statusTotal9 * CosmicInvocationsPerStatusFactor;
                total9.InvocationsLearnt = learnt9;
            }

            characterStatus.Totals.Add(total9);

            // Get total status spend on level 10 spells.
            XElement skill10 = boughtSkills
                .Where(s => s.Attribute("name") != null && s.Attribute("name").Value == "learn-rank-10-invocations")
                .FirstOrDefault();

            SectStatusTotal total10 = new SectStatusTotal() { Type = SectStatusAssignmentType.Level10, Total = 0 };

            int statusTotal10 = 0;

            if (skill10 != null && skill10.Attribute("ranks") != null && int.TryParse(skill10.Attribute("ranks").Value, out statusTotal10))
            {
                total10.Total = statusTotal10;
                total10.InvocationsAvailable = statusTotal10 * CosmicInvocationsPerStatusFactor;
                total10.InvocationsLearnt = learnt10;
            }

            characterStatus.Totals.Add(total10);

            characterStatus.Status.AddRange(statuses);

            return characterStatus;
        }
        #endregion

        #region RefreshKnownInvocations
        /// <summary>
        /// Ensure that the next list of known invocations is fresh.
        /// </summary>
        protected void RefreshKnownInvocations()
        {
            boughtInvocations = null;
        }
        #endregion

        #region GetKnownInvocations
        /// <summary>
        /// Get all the invocations known by the character.
        /// </summary>
        /// <returns>The list of known invocations.</returns>
        public List<XElement> GetKnownInvocations()
        {
            return GetKnownInvocations(false);
        }

        /// <summary>
        /// Get all the invocations known by the character.
        /// </summary>
        /// <param name="selectedOnly">Whether only selected invocations are required.</param>
        /// <returns>The list of known invocations.</returns>
        public List<XElement> GetKnownInvocations(bool selectedOnly)
        {
            if (boughtInvocations == null)
            {
                XElement power = GetPowerElement();

                boughtInvocations = power.Descendants("invocation")
                    .Where(a => selectedOnly == false || a.Attribute("selected") != null && a.Attribute("selected").Value.ToLowerInvariant().Equals("true"))
                    .ToList();
            }

            return boughtInvocations;
        }
        #endregion

        #region GetAvailableInvocations
        /// <summary>
        /// Get all the available invocations for a character.
        /// Any existing invocation choices are always included, even if they are invalid.
        /// All invocations are included that are not common invocations and thier requirements are met.
        /// </summary>
        /// <returns></returns>
        public List<InvocationItem> GetAvailableInvocations()
        {
            List<XElement> knownInvocationsList = GetKnownInvocations(true);

            // Get existing invocation choices.
            string knownInvocations =
                knownInvocationsList != null ?
                    string.Join(",", (knownInvocationsList.Select(s => s.Attribute("name").Value).ToArray())) :
                    string.Empty;


            // Get allowed guild access.
            List<SectAccessRecord> access = GetSectAccess();

            StringBuilder guilds = new StringBuilder();
            StringBuilder maxRanks = new StringBuilder();

            int characterRank = Rank();

            foreach (SectAccessRecord record in access)
            {
                string level = "5";

                if (record.Access == "High Priest")
                {
                    level = (characterRank >= 256) ? "10" : "9";
                }
                else if (record.Access == "Priest")
                    level = "8";

                if (guilds.Length > 0)
                {
                    guilds.Append(",");
                    maxRanks.Append(",");
                }

                guilds.Append(record.SourceSlug);
                maxRanks.Append(level);

            }

            IEnumerable<AvailableInvocation_Result> allowedInvocations =
                InvocationService.GetAllowedInvocations(
                    RuleSetID,
                    knownInvocations,
                    guilds.ToString(),
                    maxRanks.ToString());

            List<InvocationItem> invocations = new List<InvocationItem>();

            // Group invocations in the same invocation group into one lot with a rank range (e.g. 1-5).
            var invocationGroups =
                (from s in allowedInvocations
                 group s by s.GroupSlug into invocationGroup
                 select new
                 {
                     GroupSlug = invocationGroup.Key,
                     Item = invocationGroup.OrderBy(a => a.Rank).Last(),
                     Rank =
                        (invocationGroup.Count() > 1) ?
                            invocationGroup.OrderBy(a => a.Rank).First().Rank.ToString() + "-" + invocationGroup.OrderBy(a => a.Rank).Last().Rank.ToString() :
                            invocationGroup.First().Rank.ToString()
                 });

            foreach (var invocation in invocationGroups)
            {
                // Get known spell if present.
                XElement invocationElement =
                    knownInvocationsList != null ?
                        knownInvocationsList.Where(s => s.Attribute("name") != null && s.Attribute("name").Value == invocation.GroupSlug).FirstOrDefault() :
                        null;

                bool selected = (invocationElement != null && invocationElement.Attribute("selected") != null && invocationElement.Attribute("selected").Value == "true");

                SectStatusAssignmentType assignmentType =
                    (invocation.Item.Rank == 10) ? SectStatusAssignmentType.Level10 :
                    (invocation.Item.Rank == 9) ? SectStatusAssignmentType.Level9 :
                    SectStatusAssignmentType.Level1to8;

                invocations.Add(
                    new InvocationItem()
                    {
                        SectName = invocation.Item.SectName,
                        SectSlug = invocation.Item.SectSlug,
                        Sphere = invocation.Item.Sphere,
                        Group = invocation.GroupSlug,
                        Family = invocation.Item.Family,
                        Rank = invocation.Rank,
                        Name = invocation.Item.Name,
                        Selected = selected,
                        AssignmentType = assignmentType
                    });
            }

            return invocations;
        }
        #endregion

        #region SaveSectStatus
        /// <summary>Save the selected status allocations to the sects of power.</summary>
        /// <param name="status">The status allocations to save.</param>
        public void SaveSectStatus(SectStatus status)
        {
            XElement powerElement = GetPowerElement();

            var groups =
                (from a in status.Status
                 group a by a.SectSlug into grouped
                 select new { SectSlug = grouped.Key, Items = grouped });

            // Loop by school group and process each level type in turn.
            foreach (var item in groups)
            {
                string slug = item.SectSlug;

                int allocation1to8 = item.Items.Where(a => a.Type == SectStatusAssignmentType.Level1to8).Sum(b => b.Status);
                int allocation9 = item.Items.Where(a => a.Type == SectStatusAssignmentType.Level9).Sum(b => b.Status);
                int allocation10 = item.Items.Where(a => a.Type == SectStatusAssignmentType.Level10).Sum(b => b.Status);

                // Get school element from magic element
                XElement sect = powerElement.Elements("sect").Where(s => s.Attribute("sect-slug") != null && s.Attribute("sect-slug").Value == slug).FirstOrDefault();

                if (sect == null)
                {
                    sect = new XElement("sect", new XAttribute("sect-slug", slug));
                    powerElement.Add(sect);
                }

                // Get 1-8 attribute.
                XAttribute level1to8 = sect.Attribute("level1to8");

                if (level1to8 == null)
                {
                    level1to8 = new XAttribute("level1to8", allocation1to8.ToString());
                    sect.Add(level1to8);
                }
                else
                    level1to8.SetValue(allocation1to8.ToString());

                // Get 9 attribute.
                XAttribute level9 = sect.Attribute("level9");

                if (level9 == null)
                {
                    level9 = new XAttribute("level9", allocation9.ToString());
                    sect.Add(level9);
                }
                else
                    level9.SetValue(allocation9.ToString());

                // Get 10 attribute.
                XAttribute level10 = sect.Attribute("level10");

                if (level10 == null)
                {
                    level10 = new XAttribute("level10", allocation9.ToString());
                    sect.Add(level10);
                }
                else
                    level10.SetValue(allocation10.ToString());
            }
        }
        #endregion

        #region CanLearnInvocation
        /// <summary>
        /// Determine whether an invocation can be bought.
        /// Based on whether there are any required groups and
        /// whether they have been bought.
        /// </summary>
        /// <param name="groupName">The invocation group slug</param>
        /// <returns></returns>
        public bool CanLearnInvocation(string groupName)
        {
            bool canLearn = false;

            Invocation invocation = InvocationService.GetInvocationsByGroup(RuleSetID, groupName).FirstOrDefault();

            if (string.IsNullOrEmpty(invocation.RequiredGroupName))
                return true;

            if (invocation != null)
            {
                // get an required group name and check if it is in the known invocations list.
                List<XElement> knownInvocationList = GetKnownInvocations();

                XElement knownInvocation =
                    knownInvocationList != null ?
                        knownInvocationList.Where(a => a.Attribute("name") != null && a.Attribute("name").Value == invocation.RequiredGroupName).FirstOrDefault() :
                        null;

                // Check selected is true.
                XAttribute selectedAttibute = knownInvocation.Attribute("selected");

                canLearn = bool.Parse(selectedAttibute.Value);
            }

            return canLearn;
        }
        #endregion

        #region UpdateInvocation
        /// <summary>
        /// Set whether a invocation is selected or not.
        /// </summary>
        /// <param name="groupName">The invocation group name.</param>
        /// <param name="selected">Whether the character has selected the invocation.</param>
        public void UpdateInvocation(string groupName, bool selected)
        {
            List<XElement> knownInvocationList = GetKnownInvocations();

            XElement invocation = 
                knownInvocationList != null ?
                    knownInvocationList.Where(a => a.Attribute("name") != null && a.Attribute("name").Value == groupName).FirstOrDefault() :
                    null;

            if (invocation == null)
            {
                invocation = new XElement("invocation", new XAttribute("name", groupName));
                XElement powerElement = GetPowerElement();
                powerElement.Add(invocation);
            }

            // update selected value
            XAttribute selectedAttibute = invocation.Attribute("selected");

            if (selectedAttibute == null)
                invocation.Add(new XAttribute("selected", selected.ToString().ToLower()));
            else
                selectedAttibute.SetValue(selected.ToString().ToLower());

            // ensure that any dependent invocations are removed.
            if (!selected)
                UnselectRequiredInvocation(groupName);

            RefreshKnownInvocations();
        }
        #endregion

        #region UnselectRequiredInvocation
        /// <summary>
        /// For a given invocation group name, get any invocations chosen that are dependent upon 
        /// it and remove those also.
        /// </summary>
        /// <param name="groupName">The invocation group name unselected.</param>
        protected void UnselectRequiredInvocation(string groupName)
        {
            // get invocations from the service by group name that require this group name (slug)
            var invocations = InvocationService.GetInvocationsByRequiredGroup(RuleSetID, groupName).ToList();

            foreach (Invocation toRevoke in invocations)
            {
                List<XElement> knownInvocationList = GetKnownInvocations();

                XElement invocation =
                    knownInvocationList != null ?
                        knownInvocationList.Where(a => a.Attribute("name") != null && a.Attribute("name").Value == toRevoke.GroupSlug).FirstOrDefault() :
                        null;

                if (invocation != null)
                {
                    XAttribute selectedAttibute = invocation.Attribute("selected");

                    if (selectedAttibute == null)
                        invocation.Add(new XAttribute("selected", "false"));
                    else
                        selectedAttibute.SetValue("false");

                    // process any items dependent on this invocation.
                    UnselectRequiredInvocation(toRevoke.GroupSlug);
                }
            }
        }
        #endregion

        #region GetTotalPower
        /// <summary>Get total power available.</summary>
        /// <returns>Total power available to the character.</returns>
        public int GetTotalPower()
        {
            int power = 5;

            XElement powerSkill = GetBoughtSkill("buy-power");
            int ranks = 0;

            if (powerSkill != null &&
                powerSkill.Attribute("ranks") != null &&
                int.TryParse(powerSkill.Attribute("ranks").Value, out ranks))
            {
                power += ranks;
            }

            return power;
        }
        #endregion

        /*
         *  Armour, Abilities and Equipment.
         */

        #region GetAllArmour
        /// <summary>
        /// Get all the armour elements in the equipment section.
        /// </summary>
        /// <returns>All armour elements that are descendants of the equipment element.</returns>
        public List<ArmourItem> GetAllArmour()
        {
            XElement equipmentElement = GetEquipmentElement();

            List<ArmourItem> armours = new List<ArmourItem>();
            List<Quality> allQualities = GetQualityTypeList();

            foreach (XElement item in equipmentElement.Descendants("armour"))
            {
                ArmourItem armour = ParseEquipmentToArmour(item, allQualities);

                armours.Add(armour);
            }


            return armours;
        }
        #endregion

        #region ParseEquipmentToArmour
        /// <summary>
        /// Convert an armour element to an armour item.
        /// </summary>
        /// <param name="item">The armour item element.</param>
        /// <returns>The armour item model object.</returns>
        protected ArmourItem ParseEquipmentToArmour(XElement item, List<Quality> allQualities)
        {
            string name = item.Attribute("name") != null && !string.IsNullOrEmpty(item.Attribute("name").Value) ? item.Attribute("name").Value.Trim() : string.Empty;

            int superior = 0;
            string superiorString = item.Attribute("superior") != null && !string.IsNullOrEmpty(item.Attribute("superior").Value) ? item.Attribute("superior").Value.Trim() : string.Empty;
            int.TryParse(superiorString, out superior);

            int id = 0;
            string idString = item.Attribute("id") != null && !string.IsNullOrEmpty(item.Attribute("id").Value) ? item.Attribute("id").Value.Trim() : string.Empty;
            int.TryParse(idString, out id);

            int empowered = 0;
            string empoweredString = item.Attribute("empowered") != null && !string.IsNullOrEmpty(item.Attribute("empowered").Value) ? item.Attribute("empowered").Value.Trim() : string.Empty;
            int.TryParse(empoweredString, out empowered);

            int enscorcelled = 0;
            string enscorcelledString = item.Attribute("enscorcelled") != null && !string.IsNullOrEmpty(item.Attribute("enscorcelled").Value) ? item.Attribute("enscorcelled").Value.Trim() : string.Empty;
            int.TryParse(enscorcelledString, out enscorcelled);

            string type = item.Attribute("type") != null && !string.IsNullOrEmpty(item.Attribute("type").Value) ? item.Attribute("type").Value.Trim() : string.Empty;

            bool equipped = false;
            string equippedString = item.Attribute("equipped") != null && !string.IsNullOrEmpty(item.Attribute("equipped").Value) ? item.Attribute("equipped").Value.Trim() : string.Empty;
            bool.TryParse(equippedString, out equipped);

            ArmourType armourType = ArmourType.NonMetal;
            Enum.TryParse<ArmourType>(type, out armourType);

            string hqId = item.Attribute("hqid") != null && !string.IsNullOrEmpty(item.Attribute("hqid").Value) ? item.Attribute("hqid").Value.Trim() : string.Empty;

            ArmourItem armour = new ArmourItem()
            {
                ItemName = name,
                Superior = superior,
                Empowered = empowered,
                Ensorcelled = enscorcelled,
                Type = armourType,
                Id = id,
                Equipped = equipped,
                HQId = hqId
            };

            // Parse any special qualities for the armour.
            armour.Qualities.AddRange(GetItemQualities(item, allQualities));

            return armour;
        }
        #endregion

        #region GetItemQualities
        /// <summary>
        /// Get all the qualities for an arnmour, item or ability.
        /// </summary>
        /// <param name="parent">The item containing the qualities.</param>
        /// <returns>All the quality model object built from the data.</returns>
        protected List<QualityItem> GetItemQualities(XElement parent, List<Quality> allQualities)
        {
            List<QualityItem> qualities = new List<QualityItem>();

            foreach (XElement item in parent.Descendants("quality"))
            {
                QualityItem quality = ParseQualityItem(item, allQualities);

                qualities.Add(quality);
            }

            return qualities;
        }
        #endregion

        #region ParseQualityItem
        /// <summary>
        /// Parse a quality item XElement into a model object.
        /// </summary>
        /// <param name="item">The element to parse.</param>
        /// <param name="allQualities">All possible bonus qualities.</param>
        /// <returns></returns>
        protected QualityItem ParseQualityItem(XElement item, List<Quality> allQualities)
        {
            int id = 0;
            string idString = item.Attribute("qualityId") != null && !string.IsNullOrEmpty(item.Attribute("qualityId").Value) ? item.Attribute("qualityId").Value.Trim() : string.Empty;
            int.TryParse(idString, out id);

            string type = item.Attribute("type") != null && !string.IsNullOrEmpty(item.Attribute("type").Value) ? item.Attribute("type").Value.Trim() : string.Empty;
            BonusTypes bonusType = BonusTypes.AdrenalRank;
            Enum.TryParse<BonusTypes>(type, out bonusType);

            string value = item.Attribute("value") != null && !string.IsNullOrEmpty(item.Attribute("value").Value) ? item.Attribute("value").Value.Trim() : string.Empty;

            int quantity = 0;
            string quantityString = item.Attribute("quantity") != null && !string.IsNullOrEmpty(item.Attribute("quantity").Value) ? item.Attribute("quantity").Value.Trim() : string.Empty;
            int.TryParse(quantityString, out quantity);

            // get description.
            Quality qual = allQualities.Where(a => a.Type == bonusType).FirstOrDefault();

            if (qual == null)
                throw new ApplicationException(string.Format("Invalid special quality bonus type: {0}.", Enum.GetName(typeof(BonusTypes), bonusType)));

            string description = string.Format(qual.Format, quantity, value);

            QualityItem quality = new QualityItem()
            {
                Id = id,
                Quantity = quantity,
                Type = bonusType,
                Value = value,
                Description = description
            };

            if (item.Parent != null)
            {
                string parentName = string.Empty;

                if (item.Parent.Name.LocalName == "armour" && item.Parent.Attribute("type") != null && !string.IsNullOrEmpty(item.Parent.Attribute("type").Value))
                {
                    string armourTypeString =  item.Attribute("type").Value.Trim();

                    ArmourType armourType = ArmourType.NonMetal;
                    Enum.TryParse<ArmourType>(armourTypeString, out armourType);

                    if (armourType == ArmourType.Metal)
                        parentName = "Metal armour";
                    else if (armourType == ArmourType.NonMetal)
                        parentName = "Non-metal armour";
                    else if (armourType == ArmourType.Tabard)
                        parentName = "Tabard";
                    else if (armourType == ArmourType.Headwear)
                        parentName = "Headwear";
                }
                else if ((item.Parent.Name.LocalName == "item" || item.Parent.Name.LocalName == "ability") && item.Parent.Attribute("name") != null && !string.IsNullOrEmpty(item.Parent.Attribute("name").Value))
                {
                    parentName = item.Parent.Attribute("name").Value.Trim();
                }

                quality.ParentDescription = parentName;
            }

            return quality;
        }
        #endregion

        #region GetQualityContainer
        /// <summary>
        /// Get a quality container (item, armour or ability) by id.
        /// </summary>
        /// <param name="id">The item id.</param>
        /// <returns>The model object that respects the IQualityContainer contract.</returns>
        public IQualityContainer GetQualityContainer(int id)
        {
            XElement item = (from a in GetEquipmentElement().Elements()
                             where a.Attribute("id") != null
                             && a.Attribute("id").Value == id.ToString()
                             select a).FirstOrDefault();

            if (item == null)
                return null;

            List<Quality> allQualities = GetQualityTypeList();

            IQualityContainer container = null;

            if (item.Name.LocalName.ToLower() == "armour")
            {
                container = (IQualityContainer)ParseEquipmentToArmour(item, allQualities);
            }
            else if (item.Name.LocalName.ToLower() == "item")
            {
                container = (IQualityContainer)ParseItem(item, allQualities);
            }
            else if (item.Name.LocalName.ToLower() == "ability")
            {
                container = (IQualityContainer)ParseAbility(item, allQualities);
            }
            // todo: handle other item types.

            return container;
        }
        #endregion

        #region GetQualityTypeList
        /// <summary>
        /// Get a list of all the allowed qualities together with 
        /// any extended properties.
        /// </summary>
        /// <returns>The list of all available special qualities.</returns>
        public List<Quality> GetQualityTypeList()
        {
            List<Quality> qualities = new List<Quality>();

            qualities.Add(new Quality() { Type = BonusTypes.ArmouredDex, Description = "Increase in armoured dex", Choice = QualityChoiceType.Quantity, Format = "+{0} armoured dex" });
            qualities.Add(new Quality() { Type = BonusTypes.UnarmouredDex, Description = "Increase in unarmoured dex", Choice = QualityChoiceType.Quantity, Format = "+{0} unarmoured dex" });
            qualities.Add(new Quality() { Type = BonusTypes.PhysicalAC, Description = "Increase in physical AC", Choice = QualityChoiceType.Quantity, Format = "+{0} physical ac" });
            qualities.Add(new Quality() { Type = BonusTypes.MagicalAC, Description = "Increase in magical AC", Choice = QualityChoiceType.Quantity, Format = "+{0} magical ac" });
            qualities.Add(new Quality() { Type = BonusTypes.PowerAC, Description = "Increase in power AC", Choice = QualityChoiceType.Quantity, Format = "+{0} power ac" });
            qualities.Add(new Quality() { Type = BonusTypes.LifeRank, Description = "Extra life ranks", Choice = QualityChoiceType.Quantity, Format = "+{0} life ranks" });
            qualities.Add(new Quality() { Type = BonusTypes.SpellLevel, Description = "A castable spell level", Choice = QualityChoiceType.SpellLevel, Format = "+{0} spells of level {1}" });
            qualities.Add(new Quality() { Type = BonusTypes.Invocations1to8, Description = "Learn invocations rank 1 to 8", Choice = QualityChoiceType.Quantity, Format = "{0} invocations rank 1-8" });
            qualities.Add(new Quality() { Type = BonusTypes.Invocations9, Description = "Learn invocations rank 9", Choice = QualityChoiceType.Quantity, Format = "{0} learn invocations rank 9" });
            qualities.Add(new Quality() { Type = BonusTypes.Invocations10, Description = "Learn invocations rank 10", Choice = QualityChoiceType.Quantity, Format = "{0} learn invocations rank 10" });
            qualities.Add(new Quality() { Type = BonusTypes.Spells1to8, Description = "Learn spells level 1 to 8", Choice = QualityChoiceType.Quantity, Format = "{0} learn spells level 1-8" });
            qualities.Add(new Quality() { Type = BonusTypes.Spells9, Description = "Learn spells level 9", Choice = QualityChoiceType.Quantity, Format = "{0} learn spells level 9" });
            qualities.Add(new Quality() { Type = BonusTypes.Spells10, Description = "Learn spells level 10", Choice = QualityChoiceType.Quantity, Format = "{0} learn spells level 10" });
            qualities.Add(new Quality() { Type = BonusTypes.PowerRank, Description = "Extra power ranks", Choice = QualityChoiceType.Quantity, Format = "+{0} power" });
            qualities.Add(new Quality() { Type = BonusTypes.PsiRank, Description = "Extra psi ranks", Choice = QualityChoiceType.Quantity, Format = "+{0} psi" });
            qualities.Add(new Quality() { Type = BonusTypes.AdrenalRank, Description = "Extra adrenal ranks", Choice = QualityChoiceType.Quantity, Format = "+{0} adrenals" });
            qualities.Add(new Quality() { Type = BonusTypes.KiRank, Description = "Extra ki ranks", Choice = QualityChoiceType.Quantity, Format = "+{0} ki" });
            qualities.Add(new Quality() { Type = BonusTypes.DisarmUse, Description = "Increase in the number of disarms", Choice = QualityChoiceType.Quantity, Format = "+{0} disarm(s)" });
            qualities.Add(new Quality() { Type = BonusTypes.KnockbackUse, Description = "Increase in the number of knockbacks", Choice = QualityChoiceType.Quantity, Format = "+{0} knockback(s)" });
            qualities.Add(new Quality() { Type = BonusTypes.KnockdownUse, Description = "Increase in the number of knockdowns", Choice = QualityChoiceType.Quantity, Format = "+{0} knockdown(s)" });
            qualities.Add(new Quality() { Type = BonusTypes.StunUse, Description = "Increase in the number of stuns", Choice = QualityChoiceType.Quantity, Format = "+{0} stun(s)" });
            qualities.Add(new Quality() { Type = BonusTypes.StunShotUse, Description = "Increase in the number of stun shots", Choice = QualityChoiceType.Quantity, Format = "+{0} stun shot(s)" });
            qualities.Add(new Quality() { Type = BonusTypes.AllowTertiaryGuild, Description = "Allow a tertiary guild", Choice = QualityChoiceType.None, Format = "Allow tertiary guild" });     // allows use of tertiary guild regardless of primary and secondary.
            qualities.Add(new Quality() { Type = BonusTypes.AllowSkill, Description = "Allow access to a skill", Choice = QualityChoiceType.SkillName, Format = "All skill {1}" });             // allows the specific use of any one skill regardless of requirements.
            qualities.Add(new Quality() { Type = BonusTypes.MaxSkillRanks, Description = "Increase in number of maximum skill ranks", Choice = QualityChoiceType.SkillName, Format = "+{0} max ranks in {1}" });          // allows extra skill ranks. Value should be skill slug. Quantity the number of extra ranks.
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessMage, Description = "Allows mage level access to a school of magic", Choice = QualityChoiceType.School, Format = "Mage access to {1}" });       // allows mage level access to a school of magic.
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessWizard, Description = "Allows wizard level access to a school of magic", Choice = QualityChoiceType.School, Format = "Wizard access to {1}" });     // allows wizard level access to a school of magic.
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessSorceror, Description = "Allows sorceror level access to a school of magic", Choice = QualityChoiceType.School, Format = "Sorceror access to {1}" });   // allows sorceror level access to a school of magic.
            //qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessSpecialist, Description = "Allows specialist access to a school of magic", Choice = QualityChoiceType.School, Format = "Specialist access to {1}" }); // allows access to a school of magic specialist spell.
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessPrimary, Description = "Allows primary access to a school of magic", Choice = QualityChoiceType.School, Format = "Primary access to {1}" });    // allows access to a school of magic primary spells.
            qualities.Add(new Quality() { Type = BonusTypes.SectAccessLay, Description = "Allows lay access to a sect", Choice = QualityChoiceType.Sect, Format = "Lay access to {1}" });          // allows lay level access to a power sect.
            qualities.Add(new Quality() { Type = BonusTypes.SectAccessPriest, Description = "Allows priest access to a sect", Choice = QualityChoiceType.School, Format = "Priest access to {1}" });       // allows priest level access to a power sect.
            qualities.Add(new Quality() { Type = BonusTypes.SectAccessHighPriest, Description = "Allows high priest access to a sect", Choice = QualityChoiceType.School, Format = "High priest access to {1}" });    // allows high priest level access to a power sect.

            return qualities;
        }
        #endregion

        #region AddQuality
        /// <summary>
        /// Add a new quality to an item of equipment.
        /// </summary>
        /// <param name="itemId">The unique equipment id of the item.</param>
        /// <param name="type">The type of bonus for the quality.</param>
        /// <param name="quantity">The bonus quantity.</param>
        /// <param name="value">The bonus value.</param>
        public void AddQuality(int itemId, BonusTypes type, int quantity, string value)
        {
            // Generate the next available quality id.
            int qualityId = NextQualityId();

            // find the parent item.
            XElement item = FindItemById(itemId);

            if (item == null)
                throw new ApplicationException(string.Format("No equipment item with id {0}", itemId));

            // Create a new quality item with type, quantity and value.
            XElement quality =
                new XElement("quality",
                    new XAttribute("qualityId", NextQualityId()),
                    new XAttribute("type", Enum.GetName(typeof(BonusTypes), type)),
                    new XAttribute("quantity", quantity.ToString()),
                    new XAttribute("value", value.ToString()));

            // Add quality to parent.
            item.Add(quality);
        }
        #endregion

        #region FindItemById
        /// <summary>
        /// Find an equipment item by id.
        /// </summary>
        /// <param name="id">The unique equipment id.</param>
        /// <returns>The item or null if nt found.</returns>
        protected XElement FindItemById(int id)
        {
            XElement item = (from a in GetEquipmentElement().Elements()
                             where a.Attribute("id") != null && Convert.ToInt32(a.Attribute("id").Value) == id
                             select a).FirstOrDefault();

            return item;
        }
        #endregion

        #region NextQualityId
        /// <summary>
        /// Generate the next available quality id.
        /// All qualities have a qualityId attribute which is unique 
        /// across the whol character document.
        /// </summary>
        /// <returns>The next available quality id (max + 1).</returns>
        protected int NextQualityId()
        {
            var qualities = GetCharacterElement().Descendants("quality").ToList();

            if (qualities == null || qualities.Count == 0)
                return 1;

            int max = qualities.Select(a => Convert.ToInt32(a.Attribute("qualityId").Value)).Max();

            return max+1;
        }
        #endregion

        #region GetEquipmentByElementName
        /// <summary>
        /// Get all elements in the equipment section with a given element name.
        /// </summary>
        /// <returns>All elements that are descendants of the equipment element.</returns>
        public List<XElement> GetEquipmentByElementName(string elementName)
        {
            XElement equipmentElement = GetEquipmentElement();

            return equipmentElement.Descendants(elementName).ToList();
        }
        #endregion

        #region GetEquipmentElement
        /// <summary>
        /// Get the equipment character element.
        /// This is the root for all armour and items.
        /// If the equipment element doesn't exist, create it.
        /// </summary>
        /// <returns>The equipment element.</returns>
        public XElement GetEquipmentElement()
        {
            XElement characterElement = GetCharacterElement();

            XElement equipmentElement = characterElement.Descendants("equipment").FirstOrDefault();

            if (equipmentElement == null)
            {
                equipmentElement = new XElement("equipment");
                characterElement.Add(equipmentElement);
            }

            return equipmentElement;
        }
        #endregion

        #region UpdateArmour
        /// <summary>
        /// Add/update an armour item record.
        /// </summary>
        /// <param name="id">The item id of the armour.  If null then a new armour item.</param>
        /// <param name="name">An optional unique item name.</param>
        /// <param name="type">The type of the armour.</param>
        /// <param name="superior">The quantity of superiorness, maximum 3.</param>
        /// <param name="empowered">The quantity of empowering, maximum 10.</param>
        /// <param name="enscorcelled">The quantity of enscorcelling, maximum 10.</param>
        public void UpdateArmour(int? id, string name, ArmourType type, int superior, int empowered, int enscorcelled, string hqId)
        {
            if (superior < 0 || superior > 3)
                throw new ArgumentException("Superior can only from 0 to 3", "superior");

            if (empowered < 0 || empowered > 10)
                throw new ArgumentException("Empowered can only from 0 to 10", "empowered");

            if (enscorcelled < 0 || enscorcelled > 10)
                throw new ArgumentException("Enscorcelled can only from 0 to 10", "enscorcelled");

            XElement equipmentElement = GetEquipmentElement();
            XElement armourElement = null;

            if (id.HasValue)
                armourElement = equipmentElement.Descendants("armour").Where(a => a.Attribute("id") != null && a.Attribute("id").Value == id.Value.ToString()).FirstOrDefault();

            if (armourElement == null)
            {
                id = GenerateNextEquipmentId();
                armourElement = new XElement(
                    "armour",
                    new XAttribute("id", id.Value.ToString()),
                    new XAttribute("hqid", string.IsNullOrEmpty(hqId) ? string.Empty : hqId),
                    new XAttribute("type", Enum.GetName(typeof(ArmourType), type)),
                    new XAttribute("superior", superior.ToString()),
                    new XAttribute("empowered", empowered.ToString()),
                    new XAttribute("enscorcelled", enscorcelled.ToString()),
                    new XAttribute("equipped", "false"));

                if (!string.IsNullOrEmpty(name))
                    armourElement.Add(new XAttribute("name", name.Trim()));

                equipmentElement.Add(armourElement);
                return;
            }

            // update the item name.
            XAttribute nameAttribute = armourElement.Attribute("name");

            if (nameAttribute == null)
            {
                nameAttribute = new XAttribute("name", string.IsNullOrEmpty(name) ? string.Empty : name);
                armourElement.Add(nameAttribute);
            }
            else
                nameAttribute.SetValue(string.IsNullOrEmpty(name) ? string.Empty : name);

            // update the HQ id.
            XAttribute hqIdAttribute = armourElement.Attribute("hqid");

            if (hqIdAttribute == null)
            {
                hqIdAttribute = new XAttribute("hqid", string.IsNullOrEmpty(hqId) ? string.Empty : hqId);
                armourElement.Add(hqIdAttribute);
            }
            else
                hqIdAttribute.SetValue(string.IsNullOrEmpty(hqId) ? string.Empty : hqId);

            // update armour type.
            XAttribute typeAttribute = armourElement.Attribute("type");

            if (typeAttribute == null)
            {
                typeAttribute = new XAttribute("type", Enum.GetName(typeof(ArmourType), type));
                armourElement.Add(typeAttribute);
            }
            else
                typeAttribute.SetValue(Enum.GetName(typeof(ArmourType), type));

            // update superior value.
            XAttribute superiorAttribute = armourElement.Attribute("superior");

            if (superiorAttribute == null)
            {
                superiorAttribute = new XAttribute("superior", superior.ToString());
                armourElement.Add(superiorAttribute);
            }
            else
                superiorAttribute.SetValue(superior.ToString());

            // update empowered value.
            XAttribute empoweredAttribute = armourElement.Attribute("empowered");

            if (empoweredAttribute == null)
            {
                empoweredAttribute = new XAttribute("empowered", empowered.ToString());
                armourElement.Add(empoweredAttribute);
            }
            else
                empoweredAttribute.SetValue(empowered.ToString());

            // update enscorcelled value.
            XAttribute enscorcelledAttribute = armourElement.Attribute("enscorcelled");

            if (enscorcelledAttribute == null)
            {
                enscorcelledAttribute = new XAttribute("enscorcelled", enscorcelled.ToString());
                armourElement.Add(enscorcelledAttribute);
            }
            else
                enscorcelledAttribute.SetValue(enscorcelled.ToString());
        }
        #endregion

        #region RemoveItem
        /// <summary>
        /// Remove an item using it's unique equipment id.
        /// </summary>
        /// <param name="id">The id of the item to remove.</param>
        public void RemoveItem(int id)
        {
            XElement equipmentElement = GetEquipmentElement();
            XElement itemElement = 
                equipmentElement.Elements()
                .Where(a => a.Attribute("id") != null && a.Attribute("id").Value == id.ToString())
                .FirstOrDefault();

            if (itemElement != null)
                itemElement.Remove();
        }
        #endregion

        #region ItemEquipped
        /// <summary>
        /// Update an item as to whether it is equipped or not.
        /// </summary>
        /// <param name="id"></param>
        /// <param name="equipped"></param>
        public void ItemEquipped(int id, bool equipped)
        {
            XElement equipmentElement = GetEquipmentElement();
            XElement itemElement = 
                equipmentElement.Elements()
                .Where(a => a.Attribute("id") != null && a.Attribute("id").Value == id.ToString())
                .FirstOrDefault();

            if (itemElement == null)
                throw new ArgumentException("No armour, item or ability found with that id.");

            // if the item is an armour then un-equip all armours first.
            if (itemElement.Attribute("type") != null && (itemElement.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.Metal) ||  itemElement.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.NonMetal)))
            {
                var allArmours = equipmentElement.Descendants("armour")
                    .Where(a => a.Attribute("type") != null && 
                        (a.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.Metal) || 
                        a.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.NonMetal)));
                
                foreach (XElement armour in allArmours)
                {
                    XAttribute equipAttribute = armour.Attribute("equipped");

                    if (equipAttribute != null)
                        equipAttribute.SetValue("false");
                }
            }

            // if the item is a tabard then un-equip all tabards first.
            if (itemElement.Attribute("type") != null && (itemElement.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.Tabard) ))
            {
                var allTabards = equipmentElement.Descendants("armour")
                    .Where(a => a.Attribute("type") != null &&
                        (a.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.Tabard) ));

                foreach (XElement armour in allTabards)
                {
                    XAttribute equipAttribute = armour.Attribute("equipped");

                    if (equipAttribute != null)
                        equipAttribute.SetValue("false");
                }
            }

            // if the item is a headwear then un-equip all headwears first.
            if (itemElement.Attribute("type") != null && (itemElement.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.Headwear)))
            {
                var allheadwear = equipmentElement.Descendants("armour")
                    .Where(a => a.Attribute("type") != null &&
                        (a.Attribute("type").Value == Enum.GetName(typeof(ArmourType), ArmourType.Headwear)));

                foreach (XElement armour in allheadwear)
                {
                    XAttribute equipAttribute = armour.Attribute("equipped");

                    if (equipAttribute != null)
                        equipAttribute.SetValue("false");
                }
            }

            // update equipped value.
            XAttribute equippedAttribute = itemElement.Attribute("equipped");

            if (equippedAttribute == null)
            {
                equippedAttribute = new XAttribute("equipped", equipped.ToString().ToLower());
                itemElement.Add(equippedAttribute);
            }
            else
                equippedAttribute.SetValue(equipped.ToString().ToLower());
       }
        #endregion

        #region GenerateNextEquipmentId
        /// <summary>
        /// Generate the next sequential equipment id
        /// </summary>
        /// <returns>The next highest equipment id.</returns>
        protected int GenerateNextEquipmentId()
        {
            List<int> idList = GetEquipmentElement()
                .Elements()
                .Where(a => a.Attribute("id") != null)
                .Select(a => Convert.ToInt32(a.Attribute("id").Value))
                .ToList();

            int maxId = idList != null && idList.Count > 0 ? idList.Max() : 0;

            return ++maxId;
        }
        #endregion

        #region UpdateQuality (cut)
        ///// <summary>
        ///// Add or update a quality to/on an item.
        ///// </summary>
        ///// <param name="itemId">The id of an armour, item or ability element in the equipment section.</param>
        ///// <param name="qualityId">The unique quality id (within an item)</param>
        ///// <param name="type">The type of bonus the quality provides.</param>
        ///// <param name="value">The optional bonus value.</param>
        ///// <param name="quantity">The bonus quantity. Must be > 0.</param>
        //public void UpdateQuality(int itemId, int? qualityId, BonusTypes type, string value, int quantity)
        //{
        //    XElement equipmentElement = GetEquipmentElement();
        //    XElement itemElement =
        //        equipmentElement.Elements()
        //        .Where(a => a.Attribute("id") != null && a.Attribute("id").Value == itemId.ToString())
        //        .FirstOrDefault();

        //    if (itemElement == null)
        //        throw new ArgumentException("No armour, item or ability found with that id.");

        //    XElement qualityElement = null;

        //    if (qualityId.HasValue)
        //        qualityElement = itemElement.Elements("quality").Where(a => a.Attribute("id") != null && a.Attribute("id").Value == qualityId.Value.ToString()).FirstOrDefault();

        //    if (qualityElement == null)
        //    {
        //        qualityElement = new XElement("quality",
        //            new XAttribute("qualityId", NextQualityId()),
        //            new XAttribute("type", Enum.GetName(typeof(BonusTypes), type)),
        //            new XAttribute("value", value),
        //            new XAttribute("quantity", quantity.ToString()));
        //        return;
        //    }

        //    // update bonus type.
        //    XAttribute typeAttribute = qualityElement.Attribute("type");

        //    if (typeAttribute == null)
        //    {
        //        typeAttribute = new XAttribute("type", Enum.GetName(typeof(BonusTypes), type));
        //        qualityElement.Add(typeAttribute);
        //    }
        //    else
        //        typeAttribute.SetValue(Enum.GetName(typeof(ArmourType), type));

        //    // update bonus value.
        //    XAttribute valueAttribute = qualityElement.Attribute("value");

        //    if (valueAttribute == null)
        //    {
        //        valueAttribute = new XAttribute("value", string.IsNullOrEmpty(value) ? string.Empty : value);
        //        qualityElement.Add(valueAttribute);
        //    }
        //    else
        //        valueAttribute.SetValue(string.IsNullOrEmpty(value) ? string.Empty : value);

        //    // update bonus quantity.
        //    XAttribute quantityAttribute = qualityElement.Attribute("quantity");

        //    if (quantityAttribute == null)
        //    {
        //        quantityAttribute = new XAttribute("quantity", quantity.ToString());
        //        qualityElement.Add(quantityAttribute);
        //    }
        //    else
        //        quantityAttribute.SetValue(quantity.ToString());

        //}
        #endregion

        #region RemoveItemQuality
        /// <summary>
        /// Remove an item quality.
        /// </summary>
        /// <param name="itemId">The id of an armour, item or ability element in the equipment section.</param>
        /// <param name="qualityId">The unique quality id (within an item)</param>
        public void RemoveItemQuality(int itemId, int qualityId)
        {
             XElement equipmentElement = GetEquipmentElement();
            XElement itemElement =
                equipmentElement.Elements()
                .Where(a => a.Attribute("id") != null && a.Attribute("id").Value == itemId.ToString())
                .FirstOrDefault();

            if (itemElement == null)
                throw new ArgumentException("No armour, item or ability found with that id.");

            XElement qualityElement = null;

            qualityElement = itemElement.Elements("quality").Where(a => a.Attribute("qualityId") != null && a.Attribute("qualityId").Value == qualityId.ToString()).FirstOrDefault();

            if (qualityElement != null)
                qualityElement.Remove();
        }
        #endregion

        #region GetQualityTypes
        /// <summary>
        /// Get a list of all available qualities (bonuses).
        /// </summary>
        /// <returns>The list of available qualitites.</returns>
        public List<Quality> GetQualityTypes()
        {
            List<Quality> qualities = new List<Quality>();

            qualities.Add(new Quality() { Type = BonusTypes.AdrenalRank, Description = "A rank of adrenal" });
            qualities.Add(new Quality() { Type = BonusTypes.AllowSkill, Description = "Access to a skill" });
            qualities.Add(new Quality() { Type = BonusTypes.AllowTertiaryGuild, Description = "Allow a tertiary guild" });
            qualities.Add(new Quality() { Type = BonusTypes.ArmouredDex, Description = "A rank of Armoured Dex" });
            qualities.Add(new Quality() { Type = BonusTypes.DisarmUse, Description = "A use of the Disarm skill" });
            qualities.Add(new Quality() { Type = BonusTypes.Invocations10, Description = "A rank of Learn Rank 10 Invocations" });
            qualities.Add(new Quality() { Type = BonusTypes.Invocations1to8, Description = "A rank of Learn Rank 1-8 Invocations" });
            qualities.Add(new Quality() { Type = BonusTypes.Invocations9, Description = "A rank of Learn Rank 9 Invocations" });
            qualities.Add(new Quality() { Type = BonusTypes.KiRank, Description = "A rank of Ki" });
            qualities.Add(new Quality() { Type = BonusTypes.KnockbackUse, Description = "A Knockback use" });
            qualities.Add(new Quality() { Type = BonusTypes.KnockdownUse, Description = "A Knockdown use" });
            qualities.Add(new Quality() { Type = BonusTypes.LifeRank, Description = "A rank of life" });
            qualities.Add(new Quality() { Type = BonusTypes.MagicalAC, Description = "Magical armour class" });
            qualities.Add(new Quality() { Type = BonusTypes.MaxSkillRanks, Description = "An increase of max skill ranks" });
            qualities.Add(new Quality() { Type = BonusTypes.PhysicalAC, Description = "Physical armour class" });
            qualities.Add(new Quality() { Type = BonusTypes.PowerAC, Description = "Power armour class" });
            qualities.Add(new Quality() { Type = BonusTypes.PowerRank, Description = "A rank of power" });
            qualities.Add(new Quality() { Type = BonusTypes.PsiRank, Description = "A rank of psi" });
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessMage, Description = "Mage access to a school of magic" });
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessPrimary, Description = "Primary access to a school of magic" });
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessSorceror, Description = "Sorceror access to a school of magic" });
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessSpecialist, Description = "Specialist access to a school of magic" });
            qualities.Add(new Quality() { Type = BonusTypes.SchoolAccessWizard, Description = "Wizard access to a school of magic" });
            qualities.Add(new Quality() { Type = BonusTypes.SectAccessHighPriest, Description = "High priest access to a sect" });
            qualities.Add(new Quality() { Type = BonusTypes.SectAccessLay, Description = "Lay access to a sect" });
            qualities.Add(new Quality() { Type = BonusTypes.SectAccessPriest, Description = "Priest access to a sect" });
            qualities.Add(new Quality() { Type = BonusTypes.SpellLevel, Description = "A spell level use" });
            qualities.Add(new Quality() { Type = BonusTypes.Spells10, Description = "A rank of Learn Level 10 Spells" });
            qualities.Add(new Quality() { Type = BonusTypes.Spells1to8, Description = "A rank of Learn Level 1-8 Spells" });
            qualities.Add(new Quality() { Type = BonusTypes.Spells9, Description = "A rank of Learn Level 9 Spells" });
            qualities.Add(new Quality() { Type = BonusTypes.StunShotUse, Description = "A use of the Stun Shot skill" });
            qualities.Add(new Quality() { Type = BonusTypes.StunUse, Description = "A use of the Stun skill" });
            qualities.Add(new Quality() { Type = BonusTypes.UnarmouredDex, Description = "A rank of Unarmoured Dex" });

            return qualities;
        }
        #endregion

        #region GetArmourTypeList
        /// <summary>
        /// Get a list of all available armour types.
        /// </summary>
        /// <returns>All available armour types and description.</returns>
        public List<ArmourSelectType> GetArmourTypeList()
        {
            List<ArmourSelectType> armours = new List<ArmourSelectType>();

            armours.Add(new ArmourSelectType() { Type = ArmourType.Metal, Description = "Metal armour" });
            armours.Add(new ArmourSelectType() { Type = ArmourType.NonMetal, Description = "Non-metal armour" });
            armours.Add(new ArmourSelectType() { Type = ArmourType.Tabard, Description = "Tabard" });
            armours.Add(new ArmourSelectType() { Type = ArmourType.Headwear, Description = "Headwear" });

            return armours;
        }
        #endregion

        #region GetEquippedArmours
        /// <summary>
        /// Get all equipped armour elements.
        /// </summary>
        /// <returns></returns>
        public List<XElement> GetEquippedArmours()
        {
            List<XElement> equippedArmours = (from a in GetEquipmentElement().Elements("armour")
                                   where a.Attribute("equipped") != null &&
                                   Convert.ToBoolean(a.Attribute("equipped").Value)
                                   select a).ToList();

            return equippedArmours;
        }
        #endregion

        #region GetEquippedItems
        /// <summary>
        /// Get all equipped item elements.
        /// </summary>
        /// <returns></returns>
        public List<XElement> GetEquippedItems()
        {
            List<XElement> equippedItems = (from a in GetEquipmentElement().Elements("item")
                                              where a.Attribute("equipped") != null &&
                                              Convert.ToBoolean(a.Attribute("equipped").Value)
                                              select a).ToList();

            return equippedItems;
        }
        #endregion

        #region GetEquippedAbilities
        /// <summary>
        /// Get all equipped ability elements.
        /// </summary>
        /// <returns></returns>
        public List<XElement> GetEquippedAbilities()
        {
            List<XElement> equippedAbilities = (from a in GetEquipmentElement().Elements("ability")
                                            where a.Attribute("equipped") != null &&
                                            Convert.ToBoolean(a.Attribute("equipped").Value)
                                            select a).ToList();

            return equippedAbilities;
        }
        #endregion

        #region GetItems
        /// <summary>
        /// Get all items (items, abilities, scrolls and potions) converted into model objects.
        /// </summary>
        /// <returns>List of model obejcts for all character item and abilities.</returns>
        public List<ItemBase> GetItems()
        {
            List<ItemBase> items = new List<ItemBase>();

            List<Quality> allQualities = GetQualityTypeList();

            // get item equipment elements and parse into item model objects.
            List<XElement> elements = GetEquipmentByElementName("item");

            if (elements != null)
            {
                foreach (XElement element in elements)
                {
                    items.Add(ParseItem(element, allQualities));
                }
            }

            // get ability equipment elements and parse into ability model objects.
            elements = GetEquipmentByElementName("ability");

            if (elements != null)
            {
                foreach (XElement element in elements)
                {
                    items.Add(ParseAbility(element, allQualities));
                }
            }

            // get scroll equipment elements and parse into scroll model objects.
            elements = GetEquipmentByElementName("scroll");

            if (elements != null)
            {
                foreach (XElement element in elements)
                {
                    items.Add(ParseScroll(element));
                }
            }

            // get potion equipment elements and parse into potion model objects.
            elements = GetEquipmentByElementName("potion");

            if (elements != null)
            {
                foreach (XElement element in elements)
                {
                    items.Add(ParsePotion(element));
                }
            }
            return items;
        }
        #endregion

        #region ParseItem
        /// <summary>
        /// Create an Item model object from an item xml element.
        /// </summary>
        /// <param name="element">The parent &lt;item&gt; element.</param>
        /// <returns>The item model.</returns>
        protected ItemBase ParseItem(XElement element, List<Quality> allQualities)
        {
            string name = element.Attribute("name") != null && !string.IsNullOrEmpty(element.Attribute("name").Value) ? element.Attribute("name").Value.Trim() : string.Empty;
            string icon = element.Attribute("icon") != null && !string.IsNullOrEmpty(element.Attribute("icon").Value) ? element.Attribute("icon").Value.Trim() : string.Empty;

            int id = 0;
            string idString = element.Attribute("id") != null && !string.IsNullOrEmpty(element.Attribute("id").Value) ? element.Attribute("id").Value.Trim() : string.Empty;
            int.TryParse(idString, out id);

            bool equipped = false;
            string equippedString = element.Attribute("equipped") != null && !string.IsNullOrEmpty(element.Attribute("equipped").Value) ? element.Attribute("equipped").Value.Trim() : string.Empty;
            bool.TryParse(equippedString, out equipped);

            string hqId = element.Attribute("hqid") != null && !string.IsNullOrEmpty(element.Attribute("hqid").Value) ? element.Attribute("hqid").Value.Trim() : string.Empty;

            XElement descriptionElement = element.Element("description");
            string description = string.Empty;

            if (descriptionElement != null && !string.IsNullOrEmpty(descriptionElement.Value))
                description = descriptionElement.Value;

            XElement useElement = element.Element("use");
            string frequency = string.Empty;
            int charges = 0;
            ItemUseType useType = ItemUseType.OnceEver;

            if (useElement != null)
            {
                string type = useElement.Attribute("type") != null && !string.IsNullOrEmpty(useElement.Attribute("type").Value) ? useElement.Attribute("type").Value.Trim() : string.Empty;
                useType = (ItemUseType)Enum.Parse(typeof(ItemUseType), type);

                frequency = useElement.Attribute("frequency") != null && !string.IsNullOrEmpty(useElement.Attribute("frequency").Value) ? useElement.Attribute("frequency").Value.Trim() : string.Empty;

                string chargesString = useElement.Attribute("charges") != null && !string.IsNullOrEmpty(useElement.Attribute("charges").Value) ? useElement.Attribute("charges").Value.Trim() : string.Empty;
                int.TryParse(chargesString, out charges);
            }

            //Uri iconUri = new Uri(new Uri("/Content/Images/icons/equipment/"), icon);

            Item item = new Item()
            {
                Name = name,
                Id = id,
                HQId = hqId,
                Equipped = equipped,
                Description = description,
                Icon = new IconItem()
                {
                    File = icon,
                    RelativePath = string.Format("/Content/Images/icons/equipment/{0}", icon)
                },
                Use = useType,
                Frequency = frequency,
                Charges = charges
            };

            item.Qualities.AddRange(GetItemQualities(element, allQualities));

            return item;
        }
        #endregion

        #region ParseAbility
        /// <summary>
        /// Create an Ability model object from an item xml element.
        /// </summary>
        /// <param name="element">The parent &lt;item&gt; element.</param>
        /// <returns>The ability model.</returns>
        protected ItemBase ParseAbility(XElement element, List<Quality> allQualities)
        {
            string name = element.Attribute("name") != null && !string.IsNullOrEmpty(element.Attribute("name").Value) ? element.Attribute("name").Value.Trim() : string.Empty;
            string icon = element.Attribute("icon") != null && !string.IsNullOrEmpty(element.Attribute("icon").Value) ? element.Attribute("icon").Value.Trim() : string.Empty;

            int id = 0;
            string idString = element.Attribute("id") != null && !string.IsNullOrEmpty(element.Attribute("id").Value) ? element.Attribute("id").Value.Trim() : string.Empty;
            int.TryParse(idString, out id);

            bool equipped = false;
            string equippedString = element.Attribute("equipped") != null && !string.IsNullOrEmpty(element.Attribute("equipped").Value) ? element.Attribute("equipped").Value.Trim() : string.Empty;
            bool.TryParse(equippedString, out equipped);

            string hqId = element.Attribute("hqid") != null && !string.IsNullOrEmpty(element.Attribute("hqid").Value) ? element.Attribute("hqid").Value.Trim() : string.Empty;

            XElement descriptionElement = element.Element("description");
            string description = string.Empty;

            if (descriptionElement != null && !string.IsNullOrEmpty(descriptionElement.Value))
                description = descriptionElement.Value;

            XElement useElement = element.Element("use");
            string frequency = string.Empty;
            string recharge = string.Empty;
            ItemUseType useType = ItemUseType.OnceEver;

            if (useElement != null)
            {
                string type = useElement.Attribute("type") != null && !string.IsNullOrEmpty(useElement.Attribute("type").Value) ? useElement.Attribute("type").Value.Trim() : string.Empty;
                useType = (ItemUseType)Enum.Parse(typeof(ItemUseType), type);

                frequency = useElement.Attribute("frequency") != null && !string.IsNullOrEmpty(useElement.Attribute("frequency").Value) ? useElement.Attribute("frequency").Value.Trim() : string.Empty;
                recharge = useElement.Attribute("charges") != null && !string.IsNullOrEmpty(useElement.Attribute("charges").Value) ? useElement.Attribute("charges").Value.Trim() : string.Empty;
            }

            //Uri iconUri = new Uri(new Uri("/Content/Images/icons/equipment/"), icon);

            Ability item = new Ability()
            {
                Name = name,
                Id = id,
                HQId = hqId,
                Equipped = equipped,
                Description = description,
                Icon = new IconItem()
                {
                    File = icon,
                    RelativePath = string.Format("/Content/Images/icons/equipment/{0}", icon)
                },
                Use = useType,
                Frequency = frequency,
                Charges = recharge
            };

            item.Qualities.AddRange(GetItemQualities(element, allQualities));

            return item;
        }
        #endregion

        #region ParseScroll
        /// <summary>
        /// Create an Ability model object from an item xml element.
        /// </summary>
        /// <param name="element">The parent &lt;item&gt; element.</param>
        /// <returns>The ability model.</returns>
        protected ItemBase ParseScroll(XElement element)
        {
            string name = element.Attribute("name") != null && !string.IsNullOrEmpty(element.Attribute("name").Value) ? element.Attribute("name").Value.Trim() : string.Empty;
            string icon = element.Attribute("icon") != null && !string.IsNullOrEmpty(element.Attribute("icon").Value) ? element.Attribute("icon").Value.Trim() : string.Empty;

            int id = 0;
            string idString = element.Attribute("id") != null && !string.IsNullOrEmpty(element.Attribute("id").Value) ? element.Attribute("id").Value.Trim() : string.Empty;
            int.TryParse(idString, out id);

            bool equipped = false;
            string equippedString = element.Attribute("equipped") != null && !string.IsNullOrEmpty(element.Attribute("equipped").Value) ? element.Attribute("equipped").Value.Trim() : string.Empty;
            bool.TryParse(equippedString, out equipped);

            string hqId = element.Attribute("hqid") != null && !string.IsNullOrEmpty(element.Attribute("hqid").Value) ? element.Attribute("hqid").Value.Trim() : string.Empty;

            bool written = false;

            string contentSlug = element.Attribute("content-slug") != null && !string.IsNullOrEmpty(element.Attribute("content-slug").Value) ? element.Attribute("content-slug").Value.Trim() : string.Empty;

            written = !string.IsNullOrEmpty(contentSlug);

            //Uri iconUri = new Uri(new Uri("/Content/Images/icons/equipment/"), icon);

            Scroll item = new Scroll()
            {
                Name = name,
                Id = id,
                HQId = hqId,
                Equipped = equipped,
                Icon = new IconItem()
                {
                    File = icon,
                    RelativePath = string.Format("/Content/Images/icons/equipment/{0}", icon)
                },
                Written = written,
                ContentSlug = contentSlug
            };

            return item;
        }
        #endregion

        #region ParsePotion
        /// <summary>
        /// Create a Potion model object from a potion xml element.
        /// </summary>
        /// <param name="element">The parent &lt;item&gt; element.</param>
        /// <returns>The potion model.</returns>
        protected ItemBase ParsePotion(XElement element)
        {
            string name = element.Attribute("name") != null && !string.IsNullOrEmpty(element.Attribute("name").Value) ? element.Attribute("name").Value.Trim() : string.Empty;
            string icon = element.Attribute("icon") != null && !string.IsNullOrEmpty(element.Attribute("icon").Value) ? element.Attribute("icon").Value.Trim() : string.Empty;

            int id = 0;
            string idString = element.Attribute("id") != null && !string.IsNullOrEmpty(element.Attribute("id").Value) ? element.Attribute("id").Value.Trim() : string.Empty;
            int.TryParse(idString, out id);

            bool equipped = false;
            string equippedString = element.Attribute("equipped") != null && !string.IsNullOrEmpty(element.Attribute("equipped").Value) ? element.Attribute("equipped").Value.Trim() : string.Empty;
            bool.TryParse(equippedString, out equipped);

            string hqId = element.Attribute("hqid") != null && !string.IsNullOrEmpty(element.Attribute("hqid").Value) ? element.Attribute("hqid").Value.Trim() : string.Empty;

            XElement descriptionElement = element.Element("description");
            string description = string.Empty;

            if (descriptionElement != null && !string.IsNullOrEmpty(descriptionElement.Value))
                description = descriptionElement.Value;

            int doses = 0;
            string dosesString = element.Attribute("doses") != null && !string.IsNullOrEmpty(element.Attribute("doses").Value) ? element.Attribute("doses").Value.Trim() : string.Empty;
            int.TryParse(dosesString, out doses);

            //Uri iconUri = new Uri(new Uri("/Content/Images/icons/equipment/"), icon);

            Potion item = new Potion()
            {
                Name = name,
                Id = id,
                HQId = hqId,
                Equipped = equipped,
                Description = description,
                Icon = new IconItem()
                {
                    File = icon,
                    RelativePath = string.Format("/Content/Images/icons/equipment/{0}", icon)
                },
                Doses = doses
            };

            return item;
        }
        #endregion

        #region GetItemUseTypeList
        /// <summary>
        /// Get a list of all available armour types.
        /// </summary>
        /// <returns>All available armour types and description.</returns>
        public List<EquipmentUseType> GetItemUseTypeList()
        {
            List<EquipmentUseType> useTypes = new List<EquipmentUseType>();

            useTypes.Add(new EquipmentUseType() { Type = ItemUseType.AlwaysOn, Description = "Always on" });
            useTypes.Add(new EquipmentUseType() { Type = ItemUseType.Charged, Description = "A fixed number charges ever" });
            useTypes.Add(new EquipmentUseType() { Type = ItemUseType.OnceEver, Description = "One use ever" });
            useTypes.Add(new EquipmentUseType() { Type = ItemUseType.Recharges, Description = "Limited charges with a recharge period" });
            useTypes.Add(new EquipmentUseType() { Type = ItemUseType.Unlimited, Description = "Unlimited charges or uses" });

            return useTypes;
        }
        #endregion

        #region UpdateItem
        /// <summary>
        /// Add/update an item record.
        /// </summary>
        /// <param name="id">The item id of the armour.  If null then a new armour item.</param>
        /// <param name="name">An optional unique item name.</param>
        /// <param name="type">The type of the armour.</param>
        /// <param name="superior">The quantity of superiorness, maximum 3.</param>
        /// <param name="empowered">The quantity of empowering, maximum 10.</param>
        /// <param name="enscorcelled">The quantity of enscorcelling, maximum 10.</param>
        /// id, type, itemHqid, itemDescription, itemCharges, itemFrequency, itemIcon
        public void UpdateItem(int? id, ItemUseType type, string hqId, string name, string description, string charges, string frequency, string icon)
        {
            XElement equipmentElement = GetEquipmentElement();
            XElement itemElement = null;

            if (id.HasValue)
                itemElement = equipmentElement.Descendants("item").Where(a => a.Attribute("id") != null && a.Attribute("id").Value == id.Value.ToString()).FirstOrDefault();

            if (itemElement == null)
            {
                id = GenerateNextEquipmentId();
                itemElement = new XElement(
                    "item",
                    new XAttribute("id", id.Value.ToString()),
                    new XAttribute("hqid", string.IsNullOrEmpty(hqId) ? string.Empty : hqId),
                    new XAttribute("icon", string.IsNullOrEmpty(icon) ? string.Empty : icon),
                    new XAttribute("equipped", "false"),
                    new XElement("use", 
                        new XAttribute("type", Enum.GetName(typeof(ItemUseType), type)),
                        new XAttribute("charges", string.IsNullOrEmpty(charges) ? string.Empty : charges),
                        new XAttribute("frequency", string.IsNullOrEmpty(frequency) ? string.Empty : frequency)
                        )
                    );

                if (!string.IsNullOrEmpty(description))
                    itemElement.Add(new XElement("description", description));

                if (!string.IsNullOrEmpty(name))
                    itemElement.Add(new XAttribute("name", name.Trim()));

                equipmentElement.Add(itemElement);
                return;
            }

            // update the item name.
            XAttribute nameAttribute = itemElement.Attribute("name");

            if (nameAttribute == null)
            {
                nameAttribute = new XAttribute("name", string.IsNullOrEmpty(name) ? string.Empty : name);
                itemElement.Add(nameAttribute);
            }
            else
                nameAttribute.SetValue(string.IsNullOrEmpty(name) ? string.Empty : name);

            // update the HQ id.
            XAttribute hqIdAttribute = itemElement.Attribute("hqid");

            if (hqIdAttribute == null)
            {
                hqIdAttribute = new XAttribute("hqid", string.IsNullOrEmpty(hqId) ? string.Empty : hqId);
                itemElement.Add(hqIdAttribute);
            }
            else
                hqIdAttribute.SetValue(string.IsNullOrEmpty(hqId) ? string.Empty : hqId);


            XElement useElement = itemElement.Elements("use").FirstOrDefault();

            if (useElement == null)
            {
                useElement = new XElement("use",
                    new XAttribute("type", Enum.GetName(typeof(ItemUseType), type)));
                itemElement.Add(useElement);
            }
            else 
            {
                // update item use type.
                XAttribute typeAttribute = useElement.Attribute("type");

                if (typeAttribute == null)
                {
                    typeAttribute = new XAttribute("type", Enum.GetName(typeof(ItemUseType), type));
                    useElement.Add(typeAttribute);
                }
                else
                    typeAttribute.SetValue(Enum.GetName(typeof(ItemUseType), type));
            }

            XAttribute chargesAttribute = useElement.Attribute("charges");
            XAttribute frequencyAttribute = useElement.Attribute("frequency");

            // Handle the item use type variations.
            switch (type)
            {
                case ItemUseType.AlwaysOn:
                case ItemUseType.Unlimited:
                case ItemUseType.OnceEver:
                {
                    // no charges or frequency required.
                    if (chargesAttribute != null)
                        chargesAttribute.Remove();

                    if (frequencyAttribute != null)
                        frequencyAttribute.Remove();

                    break;
                }
                case ItemUseType.Charged:
                {
                    if (chargesAttribute == null)
                        chargesAttribute = new XAttribute("charges", charges);
                    else
                        chargesAttribute.SetValue(charges);

                    if (frequencyAttribute != null)
                        frequencyAttribute.Remove();
                    break;
                }
                default:
                {
                    if (chargesAttribute == null)
                        chargesAttribute = new XAttribute("charges", charges);
                    else
                        chargesAttribute.SetValue(charges);

                    if (frequencyAttribute == null)
                        frequencyAttribute = new XAttribute("frequency", frequency);
                    else
                        frequencyAttribute.SetValue(frequency);
                    
                    break;
                }
            }

            // update icon value.
            XAttribute iconAttribute = itemElement.Attribute("icon");

            if (iconAttribute == null)
            {
                iconAttribute = new XAttribute("icon", icon.ToString());
                itemElement.Add(iconAttribute);
            }
            else
                iconAttribute.SetValue(icon.ToString());

            // try and update description element.
            XElement descriptionElement = itemElement.Elements("description").FirstOrDefault();

            if (string.IsNullOrEmpty(description) && descriptionElement != null)
                descriptionElement.Remove();
            else if (!string.IsNullOrEmpty(description))
            {
                if (descriptionElement == null)
                {
                    descriptionElement = new XElement("description");
                    itemElement.Add(descriptionElement);
                }

                descriptionElement.SetValue(description);
            }
        }
        #endregion

        #region UpdateAbility
        /// <summary>
        /// Add/update an ability record.
        /// </summary>
        /// <param name="id">The item id of the ability.  If null then a new abilty.</param>
        /// <param name="name">An optional unique item name.</param>
        /// <param name="type">The type of the armour.</param>
        /// <param name="superior">The quantity of superiorness, maximum 3.</param>
        /// <param name="empowered">The quantity of empowering, maximum 10.</param>
        /// <param name="enscorcelled">The quantity of enscorcelling, maximum 10.</param>
        /// id, type, itemHqid, itemDescription, itemCharges, itemFrequency, itemIcon
        public void UpdateAbility(int? id, ItemUseType type, string hqId, string name, string description, string charges, string frequency, string icon)
        {
            XElement equipmentElement = GetEquipmentElement();
            XElement itemElement = null;

            if (id.HasValue)
                itemElement = equipmentElement.Descendants("ability").Where(a => a.Attribute("id") != null && a.Attribute("id").Value == id.Value.ToString()).FirstOrDefault();

            if (itemElement == null)
            {
                id = GenerateNextEquipmentId();
                itemElement = new XElement(
                    "ability",
                    new XAttribute("id", id.Value.ToString()),
                    new XAttribute("hqid", string.IsNullOrEmpty(hqId) ? string.Empty : hqId),
                    new XAttribute("icon", string.IsNullOrEmpty(icon) ? string.Empty : icon),
                    new XAttribute("equipped", "false"),
                    new XElement("use",
                        new XAttribute("type", Enum.GetName(typeof(ItemUseType), type)),
                        new XAttribute("charges", string.IsNullOrEmpty(charges) ? string.Empty : charges),
                        new XAttribute("frequency", string.IsNullOrEmpty(frequency) ? string.Empty : frequency)
                        )
                    );

                if (!string.IsNullOrEmpty(description))
                    itemElement.Add(new XElement("description", description));

                if (!string.IsNullOrEmpty(name))
                    itemElement.Add(new XAttribute("name", name.Trim()));

                equipmentElement.Add(itemElement);
                return;
            }

            // update the item name.
            XAttribute nameAttribute = itemElement.Attribute("name");

            if (nameAttribute == null)
            {
                nameAttribute = new XAttribute("name", string.IsNullOrEmpty(name) ? string.Empty : name);
                itemElement.Add(nameAttribute);
            }
            else
                nameAttribute.SetValue(string.IsNullOrEmpty(name) ? string.Empty : name);

            // update the HQ id.
            XAttribute hqIdAttribute = itemElement.Attribute("hqid");

            if (hqIdAttribute == null)
            {
                hqIdAttribute = new XAttribute("hqid", string.IsNullOrEmpty(hqId) ? string.Empty : hqId);
                itemElement.Add(hqIdAttribute);
            }
            else
                hqIdAttribute.SetValue(string.IsNullOrEmpty(hqId) ? string.Empty : hqId);


            XElement useElement = itemElement.Elements("use").FirstOrDefault();

            if (useElement == null)
            {
                useElement = new XElement("use",
                    new XAttribute("type", Enum.GetName(typeof(ItemUseType), type)));
                itemElement.Add(useElement);
            }
            else
            {
                // update item use type.
                XAttribute typeAttribute = useElement.Attribute("type");

                if (typeAttribute == null)
                {
                    typeAttribute = new XAttribute("type", Enum.GetName(typeof(ItemUseType), type));
                    useElement.Add(typeAttribute);
                }
                else
                    typeAttribute.SetValue(Enum.GetName(typeof(ItemUseType), type));
            }

            XAttribute chargesAttribute = useElement.Attribute("charges");
            XAttribute frequencyAttribute = useElement.Attribute("frequency");

            // Handle the item use type variations.
            switch (type)
            {
                case ItemUseType.AlwaysOn:
                case ItemUseType.Unlimited:
                case ItemUseType.OnceEver:
                    {
                        // no charges or frequency required.
                        if (chargesAttribute != null)
                            chargesAttribute.Remove();

                        if (frequencyAttribute != null)
                            frequencyAttribute.Remove();

                        break;
                    }
                case ItemUseType.Charged:
                    {
                        if (chargesAttribute == null)
                            chargesAttribute = new XAttribute("charges", charges);
                        else
                            chargesAttribute.SetValue(charges);

                        if (frequencyAttribute != null)
                            frequencyAttribute.Remove();
                        break;
                    }
                default:
                    {
                        if (chargesAttribute == null)
                            chargesAttribute = new XAttribute("charges", charges);
                        else
                            chargesAttribute.SetValue(charges);

                        if (frequencyAttribute == null)
                            frequencyAttribute = new XAttribute("frequency", frequency);
                        else
                            frequencyAttribute.SetValue(frequency);

                        break;
                    }
            }

            // update icon value.
            XAttribute iconAttribute = itemElement.Attribute("icon");

            if (iconAttribute == null)
            {
                iconAttribute = new XAttribute("icon", icon.ToString());
                itemElement.Add(iconAttribute);
            }
            else
                iconAttribute.SetValue(icon.ToString());

            // try and update description element.
            XElement descriptionElement = itemElement.Elements("description").FirstOrDefault();

            if (string.IsNullOrEmpty(description) && descriptionElement != null)
                descriptionElement.Remove();
            else if (!string.IsNullOrEmpty(description))
            {
                if (descriptionElement == null)
                {
                    descriptionElement = new XElement("description");
                    itemElement.Add(descriptionElement);
                }

                descriptionElement.SetValue(description);
            }
        }
        #endregion

        /*
         * Battleboard methods.
         */
        public BattleboardModel CreateBattleboardModel()
        {
            /*
             *  Get all equipped armours and reduce them to a display string.
             */
            #region Armours
            List<ArmourItem> armours = GetAllArmour().Where(a => a.Equipped == true).ToList();

            List<String> armourList = new List<string>();

            if (armours != null)
            {
                foreach (ArmourItem armour in armours)
                {
                    StringBuilder description = new StringBuilder(armour.Description);

                    string ac = string.Empty;

                    if (armour.Type == ArmourType.Metal)
                    {
                        ac = "AC 8";
                    }
                    else if (armour.Type == ArmourType.NonMetal)
                    {
                        ac = "AC 6";
                    }

                    if (!string.IsNullOrEmpty(armour.ItemName))
                    {
                        string name = armour.ItemName.Length > 30 ? armour.ItemName.Substring(0, 30) : armour.ItemName;
                        description.AppendFormat(" ({0})", name);
                    }

                    if (!string.IsNullOrEmpty(ac))
                    {
                        description.AppendFormat(", {0}", ac);
                    }

                    if (armour.Superior > 0)
                        description.AppendFormat(", superior + {0}", armour.Superior);

                    if (armour.Empowered > 0)
                        description.AppendFormat(", empowered + {0}", armour.Empowered);

                    if (armour.Ensorcelled > 0)
                        description.AppendFormat(", ensorcelled + {0}", armour.Ensorcelled);

                    armourList.Add(description.ToString());
                }
            }
            #endregion

            /*
             *  Get magic data.
             */
            #region Magic
            List<SpellLevelSlot> spellSlots = GetSpellSlots();

            bool hasFocus = false;
            bool hasMnenomic = false;

            XElement hasFocusSkill = GetBoughtSkill("make-focus");
            int ranks = 0;

            if (hasFocusSkill != null &&
                hasFocusSkill.Attribute("ranks") != null &&
                int.TryParse(hasFocusSkill.Attribute("ranks").Value, out ranks))
            {
                hasFocus = (ranks > 0);
            }

            XElement hasMnenomicSkill = GetBoughtSkill("mnemonic-enhancer");
            ranks = 0;

            if (hasMnenomicSkill != null &&
                hasMnenomicSkill.Attribute("ranks") != null &&
                int.TryParse(hasMnenomicSkill.Attribute("ranks").Value, out ranks))
            {
                hasMnenomic = (ranks > 0);
            }
            #endregion

            /*
             * Power values.
             */
            #region Power
            bool hasTalisman = false;
            bool canMeditate = false;

            XElement hasTalismanSkill = GetBoughtSkill("make-talisman");
            ranks = 0;

            if (hasTalismanSkill != null &&
                hasTalismanSkill.Attribute("ranks") != null &&
                int.TryParse(hasTalismanSkill.Attribute("ranks").Value, out ranks))
            {
                hasTalisman = (ranks > 0);
            }

            XElement meditateSkill = GetBoughtSkill("meditate");
            ranks = 0;

            if (meditateSkill != null &&
                meditateSkill.Attribute("ranks") != null &&
                int.TryParse(meditateSkill.Attribute("ranks").Value, out ranks))
            {
                canMeditate = (ranks > 0);
            }
            #endregion

            /*
             * Items and abilities.
             */
            List<ItemBase> equippedItems = GetItems().Where(a => a.Equipped == true).ToList();

            /*
             *  Create the battleboard model.
             */
            BattleboardModel model = new BattleboardModel()
            {
                CharacterName = GetName(),
                CharacterRace = Race(),
                CharacterClass = Class(),
                CharacterRank = Rank().ToString(),
                ArmourClasses = GetArmourClasses(),
                Armours = armourList,
                Spells = spellSlots,
                HasMagic = (spellSlots != null && spellSlots.Count > 0),
                HasFocus = hasFocus,
                CanMnenomic = hasMnenomic,
                HasTalisman = hasTalisman,
                CanMeditate = canMeditate,
                TotalPower = GetTotalPower(),
                TalismanPower = 0,
                Items = equippedItems,
                Life = GetLifeRecord()
            };

            return model;
        }
    }
}